Compare commits

...

35 Commits

Author SHA1 Message Date
Eric Bailey a1944006ca Remove unused script 2024-12-04 12:23:50 -06:00
Eric Bailey 11df003104 Make react-remove-scroll an explicit dep 2024-12-04 12:23:50 -06:00
Eric Bailey 3fd1b4e0e4 Replace scroll-removal handling 2024-12-04 12:23:50 -06:00
Eric Bailey 41c0cf9d34 Handle shifts in layout components 2024-12-04 12:23:49 -06:00
Eric Bailey 414aa379ea Remove bg in sidebars 2024-12-04 12:23:26 -06:00
Eric Bailey 251da0be52 Add transform to sidebars 2024-12-04 12:23:26 -06:00
Eric Bailey b2143cd5b4 Reset 2024-12-04 12:23:26 -06:00
Eric Bailey 7b5a5ccbf9 Increase width 2024-12-04 12:23:26 -06:00
Eric Bailey 1a81d135de Ope 2024-12-04 12:23:26 -06:00
Eric Bailey 415e3b9501 Use stable one-edge, update logic in RightNav 2024-12-04 12:23:26 -06:00
Eric Bailey f8f6d213a3 Gift left nav more space 2024-12-04 12:23:26 -06:00
Eric Bailey fceaef6bae Make sure scrollbars don't hide 2024-12-04 12:23:26 -06:00
Eric Bailey d53b8e58ce Fix sidebar alignment 2024-12-04 12:23:26 -06:00
Samuel Newman 0bd55e2562 fix breakpoints 2024-12-04 18:17:56 +00:00
Eric Bailey 4669e66e07 Replace empty chat layout 2024-12-04 12:10:00 -06:00
Eric Bailey de940fad9b Move ChatList header outside center 2024-12-04 12:05:50 -06:00
Eric Bailey 8cf3566c7d Remove unused var 2024-12-04 11:53:31 -06:00
Eric Bailey 73acee0381 Add web borders to Chat settings 2024-12-04 11:47:32 -06:00
Eric Bailey 3711f9353d [Layout] ChatList header (#6929)
* Replace ChatList header

* update chat settings as well

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2024-12-04 11:39:59 -06:00
Eric Bailey 5db4af49d9 Replace Hashtag header (#6928) 2024-12-04 11:39:46 -06:00
Eric Bailey 16e2da3881 [Layout] Notifications Header (#6910)
* Replace notifications screen header

* fix cropped indicator

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2024-12-04 11:39:37 -06:00
Samuel Newman 05b5148d38 set max with on header and center 2024-12-04 16:47:14 +00:00
Samuel Newman bd61b94e5a sticky headers 2024-12-04 16:30:21 +00:00
Eric Bailey 8a21350472 Fix alignment 2024-12-03 13:15:37 -06:00
Eric Bailey 48623b386b Allow two lines, fix wrapping 2024-12-03 12:23:20 -06:00
Eric Bailey e2dcb53937 Limit to one line 2024-12-03 12:09:13 -06:00
Eric Bailey 8167885aa8 Trade boilerplate for ease of use 2024-12-02 18:42:52 -06:00
Eric Bailey e5470f946f Port over FF handling of scrollbar offset 2024-12-02 18:19:42 -06:00
Eric Bailey 2e32d1dad6 Clean up Slot API 2024-12-02 17:17:50 -06:00
Eric Bailey 0aa1a0ff91 Invert web border logic for easier migration 2024-12-02 17:15:57 -06:00
Eric Bailey e40044b0b1 Remove old back button 2024-12-02 17:12:08 -06:00
Eric Bailey 080a0186c7 Replace layout components in settings screens 2024-12-02 17:12:01 -06:00
Eric Bailey 29db02cab7 Add new layout components 2024-12-02 17:11:27 -06:00
Eric Bailey 69378a393c Add computed scrollbar gutter CSS vars 2024-12-02 17:11:00 -06:00
Eric Bailey 55eae34dc8 Add common gutter styles as hook 2024-12-02 17:10:03 -06:00
36 changed files with 823 additions and 587 deletions
+1 -1
View File
@@ -40,7 +40,6 @@
}
html {
background-color: white;
scrollbar-gutter: stable both-edges;
}
@media (prefers-color-scheme: dark) {
html {
@@ -79,6 +78,7 @@
/* We need this style to prevent web dropdowns from shifting the display when opening */
body {
width: 100%;
overflow-y: scroll;
}
</style>
+1
View File
@@ -194,6 +194,7 @@
"react-native-web": "~0.19.11",
"react-native-web-webview": "^1.0.2",
"react-native-webview": "13.10.2",
"react-remove-scroll": "^2.6.0",
"react-responsive": "^9.0.2",
"react-textarea-autosize": "^8.5.3",
"rn-fetch-blob": "^0.12.0",
+1
View File
@@ -20,6 +20,7 @@ export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
export * from '#/alf/util/useGutterStyles'
export type Alf = {
themeName: ThemeName
+21
View File
@@ -0,0 +1,21 @@
import React from 'react'
import {atoms as a, useBreakpoints, ViewStyleProp} from '#/alf'
export function useGutterStyles({
top,
bottom,
}: {
top?: boolean
bottom?: boolean
} = {}) {
const {gtMobile} = useBreakpoints()
return React.useMemo<ViewStyleProp['style']>(() => {
return [
a.px_lg,
top && a.pt_md,
bottom && a.pb_md,
gtMobile && [a.px_xl, top && a.pt_lg, bottom && a.pb_lg],
]
}, [gtMobile, top, bottom])
}
+26 -23
View File
@@ -12,6 +12,7 @@ import {useLingui} from '@lingui/react'
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
import {useFocusGuards} from '@radix-ui/react-focus-guards'
import {FocusScope} from '@radix-ui/react-focus-scope'
import {RemoveScroll} from 'react-remove-scroll'
import {logger} from '#/logger'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -103,34 +104,36 @@ export function Outer({
{isOpen && (
<Portal>
<Context.Provider value={context}>
<TouchableWithoutFeedback
accessibilityHint={undefined}
accessibilityLabel={_(msg`Close active dialog`)}
onPress={handleBackgroundPress}>
<View
style={[
web(a.fixed),
a.inset_0,
a.z_10,
a.align_center,
gtMobile ? a.p_lg : a.p_md,
{overflowY: 'auto'},
]}>
<Backdrop />
<RemoveScroll>
<TouchableWithoutFeedback
accessibilityHint={undefined}
accessibilityLabel={_(msg`Close active dialog`)}
onPress={handleBackgroundPress}>
<View
style={[
a.w_full,
a.z_20,
a.justify_center,
web(a.fixed),
a.inset_0,
a.z_10,
a.align_center,
{
minHeight: web('calc(90vh - 36px)') || undefined,
},
gtMobile ? a.p_lg : a.p_md,
{overflowY: 'auto'},
]}>
{children}
<Backdrop />
<View
style={[
a.w_full,
a.z_20,
a.justify_center,
a.align_center,
{
minHeight: web('calc(90vh - 36px)') || undefined,
},
]}>
{children}
</View>
</View>
</View>
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
</RemoveScroll>
</Context.Provider>
</Portal>
)}
-100
View File
@@ -1,100 +0,0 @@
import React, {useContext, useMemo} from 'react'
import {View, ViewStyle} from 'react-native'
import {StyleProp} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a} from '#/alf'
// Every screen should have a Layout component wrapping it.
// This component provides a default padding for the top of the screen.
// This allows certain screens to avoid the top padding if they want to.
const LayoutContext = React.createContext({
withinScreen: false,
topPaddingDisabled: false,
withinScrollView: false,
})
/**
* Every screen should have a Layout.Screen component wrapping it.
* This component provides a default padding for the top of the screen
* and height/minHeight
*/
let Screen = ({
disableTopPadding = false,
style,
...props
}: React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
}): React.ReactNode => {
const {top} = useSafeAreaInsets()
const context = useMemo(
() => ({
withinScreen: true,
topPaddingDisabled: disableTopPadding,
withinScrollView: false,
}),
[disableTopPadding],
)
return (
<LayoutContext.Provider value={context}>
<View
style={[
{paddingTop: disableTopPadding ? 0 : top},
a.util_screen_outer,
style,
]}
{...props}
/>
</LayoutContext.Provider>
)
}
Screen = React.memo(Screen)
export {Screen}
let Header = (
props: React.ComponentProps<typeof ViewHeader>,
): React.ReactNode => {
const {withinScrollView} = useContext(LayoutContext)
if (!withinScrollView) {
return (
<CenteredView topBorder={false} sideBorders>
<ViewHeader showOnDesktop showBorder {...props} />
</CenteredView>
)
} else {
return <ViewHeader showOnDesktop showBorder {...props} />
}
}
Header = React.memo(Header)
export {Header}
let Content = ({
style,
contentContainerStyle,
...props
}: React.ComponentProps<typeof ScrollView> & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}): React.ReactNode => {
const context = useContext(LayoutContext)
const newContext = useMemo(
() => ({...context, withinScrollView: true}),
[context],
)
return (
<LayoutContext.Provider value={newContext}>
<ScrollView
style={[a.flex_1, style]}
contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]}
{...props}
/>
</LayoutContext.Provider>
)
}
Content = React.memo(Content)
export {Content}
+167
View File
@@ -0,0 +1,167 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types'
import {isIOS} from '#/platform/detection'
import {useSetDrawerOpen} from '#/state/shell'
import {
atoms as a,
TextStyleProp,
useBreakpoints,
useGutterStyles,
useTheme,
web,
} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {Text} from '#/components/Typography'
const BUTTON_VISUAL_ALIGNMENT_OFFSET = 3
const BUTTON_SIZE = 34 // small button
export function Outer({children}: {children: React.ReactNode}) {
const t = useTheme()
const gutter = useGutterStyles()
const {gtMobile} = useBreakpoints()
return (
<View
style={[
a.w_full,
a.border_b,
a.flex_row,
a.align_center,
a.gap_sm,
gutter,
a.py_sm,
t.atoms.border_contrast_low,
gtMobile && [a.mx_auto, {maxWidth: 600}],
web({
transform: [
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}>
{children}
</View>
)
}
export function Content({children}: {children: React.ReactNode}) {
return (
<View style={[a.flex_1, a.justify_center, isIOS && a.align_center]}>
{children}
</View>
)
}
export function Slot({children}: {children?: React.ReactNode}) {
return (
<View
style={[
a.z_50,
{
width: BUTTON_SIZE,
},
]}>
{children}
</View>
)
}
export function BackButton() {
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
return (
<Slot>
<Button
label={_(msg`Go back`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
onPress={onPressBack}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
<ButtonIcon icon={ArrowLeft} size="lg" />
</Button>
</Slot>
)
}
export function MenuButton() {
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const {gtMobile} = useBreakpoints()
const onPress = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
return gtMobile ? null : (
<Slot>
<Button
label={_(msg`Open drawer menu`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
onPress={onPress}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
<ButtonIcon icon={Menu} size="lg" />
</Button>
</Slot>
)
}
export function TitleText({
children,
style,
}: {children: React.ReactNode} & TextStyleProp) {
const {gtMobile} = useBreakpoints()
return (
<Text
style={[
a.text_lg,
a.font_heavy,
a.leading_tight,
isIOS && a.text_center,
gtMobile && [a.text_xl],
style,
]}
numberOfLines={2}>
{children}
</Text>
)
}
export function SubtitleText({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<Text
style={[
a.text_sm,
a.leading_snug,
isIOS && a.text_center,
t.atoms.text_contrast_medium,
]}
numberOfLines={2}>
{children}
</Text>
)
}
+194
View File
@@ -0,0 +1,194 @@
import React, {useContext, useMemo} from 'react'
import {StyleSheet, View, ViewProps, ViewStyle} from 'react-native'
import {StyleProp} from 'react-native'
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
} from 'react-native-keyboard-controller'
import Animated, {AnimatedScrollViewProps} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {isWeb} from '#/platform/detection'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
export * as Header from '#/components/Layout/Header'
// Every screen should have a Layout component wrapping it.
// This component provides a default padding for the top of the screen.
// This allows certain screens to avoid the top padding if they want to.
const LayoutContext = React.createContext({
withinScreen: false,
topPaddingDisabled: false,
withinScrollView: false,
})
export type ScreenProps = React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
temp__enableWebBorders?: boolean
}
/**
* Every screen should have a Layout.Screen component wrapping it.
* This component provides a default padding for the top of the screen
* and height/minHeight
*/
export const Screen = React.memo(function Screen({
disableTopPadding = false,
style,
temp__enableWebBorders,
...props
}: ScreenProps) {
const {top} = useSafeAreaInsets()
const context = useMemo(
() => ({
withinScreen: true,
topPaddingDisabled: disableTopPadding,
withinScrollView: false,
}),
[disableTopPadding],
)
return (
<LayoutContext.Provider value={context}>
{isWeb && temp__enableWebBorders && <WebCenterBorders />}
<View
style={[
{paddingTop: disableTopPadding ? 0 : top},
a.util_screen_outer,
style,
]}
{...props}
/>
</LayoutContext.Provider>
)
})
export type ContentProps = AnimatedScrollViewProps & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}
export const Content = React.memo(function Content({
children,
style,
contentContainerStyle,
...props
}: ContentProps) {
const context = useContext(LayoutContext)
const newContext = useMemo(
() => ({...context, withinScrollView: true}),
[context],
)
return (
<LayoutContext.Provider value={newContext}>
<Animated.ScrollView
id="content"
style={[styles.scrollViewCommonStyles, style]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
{...props}>
{isWeb ? (
// @ts-ignore web only -esb
<Center>{children}</Center>
) : (
children
)}
</Animated.ScrollView>
</LayoutContext.Provider>
)
})
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
children: React.ReactNode
contentContainerStyle?: StyleProp<ViewStyle>
}
export const KeyboardAwareContent = React.forwardRef(function LayoutScrollView(
{children, style, contentContainerStyle, ...props}: KeyboardAwareContentProps,
_ref: React.Ref<typeof KeyboardAwareScrollView>,
) {
return (
<KeyboardAwareScrollView
style={[styles.scrollViewCommonStyles, style]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
keyboardShouldPersistTaps="handled"
{...props}>
{isWeb ? <Center>{children}</Center> : children}
</KeyboardAwareScrollView>
)
})
export const WebCenterBorders = React.forwardRef(function LayoutContent() {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return gtMobile ? (
<View
style={[
a.fixed,
a.inset_0,
a.border_l,
a.border_r,
t.atoms.border_contrast_low,
web({
width: 602,
left: '50%',
transform: [
{
translateX: '-50%',
},
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}
/>
) : null
})
export const Center = React.forwardRef(function LayoutContent(
{children, style, ...props}: ViewProps,
ref: React.Ref<View>,
) {
const {gtMobile} = useBreakpoints()
return (
<View
ref={ref}
style={[
a.util_screen_outer,
a.w_full,
a.mx_auto,
gtMobile && {
maxWidth: 600,
},
style,
web({
transform: [
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}
{...props}>
{children}
</View>
)
})
const styles = StyleSheet.create({
scrollViewCommonStyles: {
width: '100%',
},
scrollViewContentContainer: {
paddingBottom: 100,
},
})
-31
View File
@@ -1,31 +0,0 @@
import {useEffect} from 'react'
import {isWeb} from '#/platform/detection'
let refCount = 0
function incrementRefCount() {
if (refCount === 0) {
document.body.style.overflow = 'hidden'
document.documentElement.style.scrollbarGutter = 'auto'
}
refCount++
}
function decrementRefCount() {
refCount--
if (refCount === 0) {
document.body.style.overflow = ''
document.documentElement.style.scrollbarGutter = ''
}
}
export function useWebBodyScrollLock(isLockActive: boolean) {
useEffect(() => {
if (!isWeb || !isLockActive) {
return
}
incrementRefCount()
return () => decrementRefCount()
})
}
+28 -25
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {ListRenderItemInfo, Pressable, View} from 'react-native'
import {ListRenderItemInfo, View} from 'react-native'
import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -13,16 +13,16 @@ import {shareUrl} from '#/lib/sharing'
import {cleanError} from '#/lib/strings/errors'
import {sanitizeHandle} from '#/lib/strings/handles'
import {enforceLen} from '#/lib/strings/helpers'
import {isNative, isWeb} from '#/platform/detection'
import {isWeb} from '#/platform/detection'
import {useSearchPostsQuery} from '#/state/queries/search-posts'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {Pager} from '#/view/com/pager/Pager'
import {TabBar} from '#/view/com/pager/TabBar'
import {Post} from '#/view/com/post/Post'
import {List} from '#/view/com/util/List'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
import {Button, ButtonIcon} from '#/components/Button'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
import * as Layout from '#/components/Layout'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
@@ -111,27 +111,30 @@ export default function HashtagScreen({
return (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ViewHeader
showOnDesktop
title={headerTitle}
subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined}
canGoBack
renderButton={
isNative
? () => (
<Pressable
accessibilityRole="button"
onPress={onShare}
hitSlop={HITSLOP_10}>
<ArrowOutOfBox_Stroke2_Corner0_Rounded
size="lg"
onPress={onShare}
/>
</Pressable>
)
: undefined
}
/>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText>
{author && (
<Layout.Header.SubtitleText>
{_(msg`From @${sanitizedAuthor}`)}
</Layout.Header.SubtitleText>
)}
</Layout.Header.Content>
<Layout.Header.Slot>
<Button
label={_(msg`Share`)}
size="small"
variant="ghost"
color="primary"
shape="round"
onPress={onShare}
hitSlop={HITSLOP_10}
style={[{right: -3}]}>
<ButtonIcon icon={Share} size="md" />
</Button>
</Layout.Header.Slot>
</Layout.Header.Outer>
</CenteredView>
<Pager
onPageSelected={onPageSelected}
+83 -131
View File
@@ -16,8 +16,6 @@ import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
import {useMessagesEventBus} from '#/state/messages/events'
import {useListConvosQuery} from '#/state/queries/messages/list-converations'
import {List} from '#/view/com/util/List'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {DialogControlProps, useDialogControl} from '#/components/Dialog'
@@ -49,7 +47,6 @@ export function MessagesScreen({navigation, route}: Props) {
const {_} = useLingui()
const t = useTheme()
const newChatControl = useDialogControl()
const {gtMobile} = useBreakpoints()
const pushToConversation = route.params?.pushToConversation
// Whenever we have `pushToConversation` set, it means we pressed a notification for a chat without being on
@@ -81,21 +78,6 @@ export function MessagesScreen({navigation, route}: Props) {
}, [messagesBus, isActive]),
)
const renderButton = useCallback(() => {
return (
<Link
to="/messages/settings"
label={_(msg`Chat settings`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
style={[a.justify_center]}>
<SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
</Link>
)
}, [_, t])
const initialNumToRender = useInitialNumToRender({minItemHeight: 80})
const [isPTRing, setIsPTRing] = useState(false)
@@ -144,28 +126,11 @@ export function MessagesScreen({navigation, route}: Props) {
[navigation],
)
const onNavigateToSettings = useCallback(() => {
navigation.navigate('MessagesSettings')
}, [navigation])
if (conversations.length < 1) {
return (
<Layout.Screen>
<CenteredView sideBorders={gtMobile} style={[a.h_full_vh]}>
{gtMobile ? (
<DesktopHeader
newChatControl={newChatControl}
onNavigateToSettings={onNavigateToSettings}
/>
) : (
<ViewHeader
title={_(msg`Messages`)}
renderButton={renderButton}
showBorder
canGoBack={false}
/>
)}
<Layout.Screen temp__enableWebBorders>
<Header newChatControl={newChatControl} />
<Layout.Center>
{isLoading ? (
<View style={[a.align_center, a.pt_3xl, web({paddingTop: '10vh'})]}>
<Loader size="xl" />
@@ -227,7 +192,7 @@ export function MessagesScreen({navigation, route}: Props) {
)}
</>
)}
</CenteredView>
</Layout.Center>
{!isLoading && !isError && (
<NewChat onNewChat={onNewChat} control={newChatControl} />
@@ -237,106 +202,93 @@ export function MessagesScreen({navigation, route}: Props) {
}
return (
<Layout.Screen testID="messagesScreen">
{!gtMobile && (
<ViewHeader
title={_(msg`Messages`)}
renderButton={renderButton}
showBorder
canGoBack={false}
<Layout.Screen testID="messagesScreen" temp__enableWebBorders>
<Header newChatControl={newChatControl} />
<Layout.Center>
<NewChat onNewChat={onNewChat} control={newChatControl} />
<List
data={conversations}
renderItem={renderItem}
keyExtractor={keyExtractor}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
style={{borderColor: 'transparent'}}
hasNextPage={hasNextPage}
showEndMessage={true}
endMessageText={_(msg`No more conversations to show`)}
/>
}
onEndReachedThreshold={isNative ? 1.5 : 0}
initialNumToRender={initialNumToRender}
windowSize={11}
// @ts-ignore our .web version only -sfn
desktopFixedHeight
sideBorders={false}
/>
)}
<NewChat onNewChat={onNewChat} control={newChatControl} />
<List
data={conversations}
renderItem={renderItem}
keyExtractor={keyExtractor}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
ListHeaderComponent={
<DesktopHeader
newChatControl={newChatControl}
onNavigateToSettings={onNavigateToSettings}
/>
}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
style={{borderColor: 'transparent'}}
hasNextPage={hasNextPage}
showEndMessage={true}
endMessageText={_(msg`No more conversations to show`)}
/>
}
onEndReachedThreshold={isNative ? 1.5 : 0}
initialNumToRender={initialNumToRender}
windowSize={11}
// @ts-ignore our .web version only -sfn
desktopFixedHeight
/>
</Layout.Center>
</Layout.Screen>
)
}
function DesktopHeader({
newChatControl,
onNavigateToSettings,
}: {
newChatControl: DialogControlProps
onNavigateToSettings: () => void
}) {
const t = useTheme()
function Header({newChatControl}: {newChatControl: DialogControlProps}) {
const {_} = useLingui()
const {gtMobile, gtTablet} = useBreakpoints()
const {gtMobile} = useBreakpoints()
if (!gtMobile) {
return null
}
const settingsLink = (
<Link
to="/messages/settings"
label={_(msg`Chat settings`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
style={[a.justify_center]}>
<ButtonIcon icon={SettingsSlider} size="md" />
</Link>
)
return (
<View
style={[
t.atoms.bg,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.px_lg,
a.pr_md,
a.py_sm,
a.border_b,
t.atoms.border_contrast_low,
]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Messages</Trans>
</Text>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
<Button
label={_(msg`Message settings`)}
color="secondary"
size="small"
variant="ghost"
shape="square"
onPress={onNavigateToSettings}>
<SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
</Button>
{gtTablet && (
<Button
label={_(msg`New chat`)}
color="primary"
size="small"
variant="solid"
onPress={newChatControl.open}>
<ButtonIcon icon={Plus} position="left" />
<ButtonText>
<Trans>New chat</Trans>
</ButtonText>
</Button>
)}
</View>
</View>
<Layout.Header.Outer>
{gtMobile ? (
<>
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Messages</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
{settingsLink}
<Button
label={_(msg`New chat`)}
color="primary"
size="small"
variant="solid"
onPress={newChatControl.open}>
<ButtonIcon icon={Plus} position="left" />
<ButtonText>
<Trans>New chat</Trans>
</ButtonText>
</Button>
</View>
</>
) : (
<>
<Layout.Header.MenuButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Messages</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot>{settingsLink}</Layout.Header.Slot>
</>
)}
</Layout.Header.Outer>
)
}
+12 -6
View File
@@ -10,8 +10,6 @@ import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declarat
import {useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Divider} from '#/components/Divider'
@@ -56,9 +54,17 @@ export function MessagesSettingsScreen({}: Props) {
)
return (
<Layout.Screen testID="messagesSettingsScreen">
<ScrollView stickyHeaderIndices={[0]}>
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
<Layout.Screen testID="messagesSettingsScreen" temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Chat Settings</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<View style={[a.p_lg, a.gap_md]}>
<Text style={[a.text_lg, a.font_bold]}>
<Trans>Allow new messages from</Trans>
@@ -142,7 +148,7 @@ export function MessagesSettingsScreen({}: Props) {
</>
)}
</View>
</ScrollView>
</Layout.Content>
</Layout.Screen>
)
}
+10 -2
View File
@@ -20,8 +20,16 @@ export function AboutSettingsScreen({}: Props) {
const {_} = useLingui()
return (
<Layout.Screen>
<Layout.Header title={_(msg`About`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>About</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
+10 -2
View File
@@ -38,8 +38,16 @@ export function AccessibilitySettingsScreen({}: Props) {
const setLargeAltBadgeEnabled = useSetLargeAltBadgeEnabled()
return (
<Layout.Screen>
<Layout.Header title={_(msg`Accessibility`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Accessibility</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Group contentContainerStyle={[a.gap_sm]}>
+10 -2
View File
@@ -37,8 +37,16 @@ export function AccountSettingsScreen({}: Props) {
const deactivateAccountControl = useDialogControl()
return (
<Layout.Screen>
<Layout.Header title={_(msg`Account`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Account</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>
+11 -3
View File
@@ -1,7 +1,7 @@
import React from 'react'
import {Alert, View} from 'react-native'
import {Image} from 'expo-image'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import * as AppIcon from '@mozzius/expo-dynamic-app-icon'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -19,8 +19,16 @@ export function AppIconSettingsScreen({}: Props) {
const sets = useAppIconSets()
return (
<Layout.Screen>
<Layout.Header title={_('App Icon')} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>App Icon</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content
contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}>
<Text style={[a.text_lg, a.font_heavy]}>Defaults</Text>
+10 -2
View File
@@ -43,8 +43,16 @@ export function AppPasswordsScreen({}: Props) {
const createAppPasswordControl = useDialogControl()
return (
<Layout.Screen testID="AppPasswordsScreen">
<Layout.Header title={_(msg`App Passwords`)} />
<Layout.Screen testID="AppPasswordsScreen" temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>App Passwords</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
{error ? (
<ErrorScreen
+10 -2
View File
@@ -78,8 +78,16 @@ export function AppearanceSettingsScreen({}: Props) {
return (
<LayoutAnimationConfig skipExiting skipEntering>
<Layout.Screen testID="preferencesThreadsScreen">
<Layout.Header title={_(msg`Appearance`)} />
<Layout.Screen testID="preferencesThreadsScreen" temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Appearance</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<AppearanceToggleButtonGroup
@@ -31,8 +31,16 @@ export function ContentAndMediaSettingsScreen({}: Props) {
const setUseInAppBrowser = useSetInAppBrowser()
return (
<Layout.Screen>
<Layout.Header title={_(msg`Content and Media`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Content & Media</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
@@ -1,7 +1,6 @@
import {Fragment} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/macro'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {
@@ -23,10 +22,19 @@ type Props = NativeStackScreenProps<
'PreferencesExternalEmbeds'
>
export function ExternalMediaPreferencesScreen({}: Props) {
const {_} = useLingui()
return (
<Layout.Screen testID="externalMediaPreferencesScreen">
<Layout.Header title={_(msg`External Media Preferences`)} />
<Layout.Screen
testID="externalMediaPreferencesScreen"
temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>External Media Preferences</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>
@@ -45,8 +45,18 @@ export function FollowingFeedPreferencesScreen({}: Props) {
)
return (
<Layout.Screen testID="followingFeedPreferencesScreen">
<Layout.Header title={_(msg`Following Feed Preferences`)} />
<Layout.Screen
testID="followingFeedPreferencesScreen"
temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Following Feed Preferences</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>
+10 -2
View File
@@ -63,8 +63,16 @@ export function LanguageSettingsScreen({}: Props) {
}, [langPrefs.contentLanguages])
return (
<Layout.Screen testID="PreferencesLanguagesScreen">
<Layout.Header title={_(msg`Languages`)} />
<Layout.Screen testID="PreferencesLanguagesScreen" temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Languages</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Group iconInset={false}>
+10 -2
View File
@@ -32,8 +32,16 @@ export function NotificationSettingsScreen({}: Props) {
: serverPriority
return (
<Layout.Screen>
<Layout.Header title={_(msg`Notification Settings`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Notification Settings</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
{isQueryError ? (
<Error
@@ -28,8 +28,16 @@ export function PrivacyAndSecuritySettingsScreen({}: Props) {
const {currentAccount} = useSession()
return (
<Layout.Screen>
<Layout.Header title={_(msg`Privacy and Security`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Privacy and Security</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>
+10 -2
View File
@@ -72,8 +72,16 @@ export function SettingsScreen({}: Props) {
const [showDevOptions, setShowDevOptions] = useState(false)
return (
<Layout.Screen>
<Layout.Header title={_(msg`Settings`)} />
<Layout.Screen temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Settings</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<View
+10 -2
View File
@@ -37,8 +37,16 @@ export function ThreadPreferencesScreen({}: Props) {
)
return (
<Layout.Screen testID="threadPreferencesScreen">
<Layout.Header title={_(msg`Thread Preferences`)} />
<Layout.Screen testID="threadPreferencesScreen" temp__enableWebBorders>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Thread Preferences</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Group>
+8 -7
View File
@@ -15,8 +15,8 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {RemoveScroll} from 'react-remove-scroll'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {colors, s} from '#/lib/styles'
import {useLightbox, useLightboxControls} from '#/state/lightbox'
@@ -28,7 +28,6 @@ export function Lightbox() {
const {activeLightbox} = useLightbox()
const {closeLightbox} = useLightboxControls()
const isActive = !!activeLightbox
useWebBodyScrollLock(isActive)
if (!isActive) {
return null
@@ -37,11 +36,13 @@ export function Lightbox() {
const initialIndex = activeLightbox.index
const imgs = activeLightbox.images
return (
<LightboxInner
imgs={imgs}
initialIndex={initialIndex}
onClose={closeLightbox}
/>
<RemoveScroll>
<LightboxInner
imgs={imgs}
initialIndex={initialIndex}
onClose={closeLightbox}
/>
</RemoveScroll>
)
}
+3 -4
View File
@@ -1,8 +1,8 @@
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {RemoveScroll} from 'react-remove-scroll'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import type {Modal as ModalIface} from '#/state/modals'
import {useModalControls, useModals} from '#/state/modals'
@@ -22,18 +22,17 @@ import * as VerifyEmailModal from './VerifyEmail'
export function ModalsContainer() {
const {isModalActive, activeModals} = useModals()
useWebBodyScrollLock(isModalActive)
if (!isModalActive) {
return null
}
return (
<>
<RemoveScroll>
{activeModals.map((modal, i) => (
<Modal key={`modal-${i}`} modal={modal} />
))}
</>
</RemoveScroll>
)
}
+3 -9
View File
@@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {cleanError} from '#/lib/strings/errors'
import {s} from '#/lib/styles'
import {logger} from '#/logger'
@@ -46,7 +45,6 @@ export function Feed({
const [isPTRing, setIsPTRing] = React.useState(false)
const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
@@ -133,11 +131,7 @@ export function Feed({
)
} else if (item === LOADING_ITEM) {
return (
<View
style={[
pal.border,
!isTabletOrMobile && {borderTopWidth: StyleSheet.hairlineWidth},
]}>
<View style={[pal.border]}>
<NotificationFeedLoadingPlaceholder />
</View>
)
@@ -146,11 +140,11 @@ export function Feed({
<FeedItem
item={item}
moderationOpts={moderationOpts!}
hideTopBorder={index === 0 && isTabletOrMobile}
hideTopBorder={index === 0}
/>
)
},
[moderationOpts, isTabletOrMobile, _, onPressRetryLoadMore, pal.border],
[moderationOpts, _, onPressRetryLoadMore, pal.border],
)
const FeedFooter = React.useCallback(
+56 -102
View File
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -6,7 +6,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {ComposeIcon2} from '#/lib/icons'
import {
NativeStackScreenProps,
@@ -14,7 +13,7 @@ import {
} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {isNative, isWeb} from '#/platform/detection'
import {emitSoftReset, listenSoftReset} from '#/state/events'
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
import {
@@ -29,28 +28,25 @@ import {FAB} from '#/view/com/util/fab/FAB'
import {ListMethods} from '#/view/com/util/List'
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
import * as Layout from '#/components/Layout'
import {Link} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
type Props = NativeStackScreenProps<
NotificationsTabNavigatorParams,
'Notifications'
>
export function NotificationsScreen({route: {params}}: Props) {
const t = useTheme()
const {gtTablet} = useBreakpoints()
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
const scrollElRef = React.useRef<ListMethods>(null)
const t = useTheme()
const {isDesktop} = useWebMediaQueries()
const queryClient = useQueryClient()
const unreadNotifs = useUnreadNotifications()
const unreadApi = useUnreadNotificationsApi()
@@ -110,102 +106,60 @@ export function NotificationsScreen({route: {params}}: Props) {
return listenSoftReset(onPressLoadLatest)
}, [onPressLoadLatest, isScreenFocused])
const renderButton = useCallback(() => {
return (
<Link
to="/notifications/settings"
label={_(msg`Notification settings`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
style={[a.justify_center]}>
<SettingsIcon size="md" style={t.atoms.text_contrast_medium} />
</Link>
)
}, [_, t])
const ListHeaderComponent = React.useCallback(() => {
if (isDesktop) {
return (
<View
style={[
t.atoms.bg,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.px_lg,
a.pr_md,
a.py_sm,
]}>
<Button
label={_(msg`Notifications`)}
accessibilityHint={_(msg`Refresh notifications`)}
onPress={emitSoftReset}>
{({hovered, pressed}) => (
<Text
style={[
a.text_2xl,
a.font_bold,
(hovered || pressed) && a.underline,
]}>
<Trans>Notifications</Trans>
{hasNew && (
<View
style={{
left: 4,
top: -8,
backgroundColor: t.palette.primary_500,
width: 8,
height: 8,
borderRadius: 4,
}}
/>
)}
</Text>
)}
</Button>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
{isLoadingLatest ? <Loader size="md" /> : <></>}
{renderButton()}
</View>
</View>
)
}
return <></>
}, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest])
const renderHeaderSpinner = React.useCallback(() => {
return (
<View
style={[
{width: 30, height: 20},
a.flex_row,
a.align_center,
a.justify_end,
a.gap_md,
]}>
{isLoadingLatest ? <Loader width={20} /> : <></>}
{renderButton()}
</View>
)
}, [renderButton, isLoadingLatest])
return (
<Layout.Screen testID="notificationsScreen">
<CenteredView style={[a.flex_1, {paddingTop: 2}]} sideBorders={true}>
<ViewHeader
title={_(msg`Notifications`)}
canGoBack={false}
showBorder={true}
renderButton={renderHeaderSpinner}
/>
<Layout.Screen testID="notificationsScreen" temp__enableWebBorders>
<Layout.Center>
<Layout.Header.Outer>
<Layout.Header.MenuButton />
<Layout.Header.Content>
<Button
label={_(msg`Notifications`)}
accessibilityHint={_(msg`Refresh notifications`)}
onPress={emitSoftReset}
style={[a.justify_start]}>
{({hovered}) => (
<Layout.Header.TitleText
style={[a.w_full, hovered && a.underline]}>
<Trans>Notifications</Trans>
{isWeb && gtTablet && hasNew && (
<View
style={[
a.rounded_full,
{
width: 8,
height: 8,
bottom: 3,
left: 6,
backgroundColor: t.palette.primary_500,
},
]}
/>
)}
</Layout.Header.TitleText>
)}
</Button>
</Layout.Header.Content>
<Layout.Header.Slot>
<Link
to="/notifications/settings"
label={_(msg`Notification settings`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
style={[a.justify_center]}>
<ButtonIcon
icon={isLoadingLatest ? Loader : SettingsIcon}
size="lg"
/>
</Link>
</Layout.Header.Slot>
</Layout.Header.Outer>
<MainScrollProvider>
<Feed
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef}
ListHeaderComponent={ListHeaderComponent}
overridePriorityNotifications={params?.show === 'all'}
/>
</MainScrollProvider>
@@ -224,7 +178,7 @@ export function NotificationsScreen({route: {params}}: Props) {
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</CenteredView>
</Layout.Center>
</Layout.Screen>
)
}
+6 -4
View File
@@ -3,8 +3,8 @@ import {StyleSheet, View} from 'react-native'
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
import {useFocusGuards} from '@radix-ui/react-focus-guards'
import {FocusScope} from '@radix-ui/react-focus-scope'
import {RemoveScroll} from 'react-remove-scroll'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useModals} from '#/state/modals'
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
import {
@@ -20,8 +20,6 @@ export function Composer({}: {winHeight: number}) {
const state = useComposerState()
const isActive = !!state
useWebBodyScrollLock(isActive)
// rendering
// =
@@ -29,7 +27,11 @@ export function Composer({}: {winHeight: number}) {
return <View />
}
return <Inner state={state} />
return (
<RemoveScroll>
<Inner state={state} />
</RemoveScroll>
)
}
function Inner({state}: {state: ComposerOpts}) {
+24 -53
View File
@@ -1,9 +1,6 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {StyleSheet, View} from 'react-native'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
@@ -14,9 +11,9 @@ import {
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {getCurrentRoute, isStateAtTabRoot, isTab} from '#/lib/routes/helpers'
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
import {makeProfileLink} from '#/lib/routes/links'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {CommonNavigatorParams} from '#/lib/routes/types'
import {isInvalidHandle} from '#/lib/strings/handles'
import {emitSoftReset} from '#/state/events'
import {useFetchHandle} from '#/state/queries/handle'
@@ -101,47 +98,6 @@ function ProfileCard() {
)
}
const HIDDEN_BACK_BNT_ROUTES = ['StarterPackWizard', 'StarterPackEdit']
function BackBtn() {
const {isTablet} = useWebMediaQueries()
const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>()
const {_} = useLingui()
const shouldShow = useNavigationState(
state =>
!isStateAtTabRoot(state) &&
!HIDDEN_BACK_BNT_ROUTES.includes(getCurrentRoute(state).name),
)
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
if (!shouldShow || isTablet) {
return <></>
}
return (
<TouchableOpacity
testID="viewHeaderBackOrMenuBtn"
onPress={onPressBack}
style={styles.backBtn}
accessibilityRole="button"
accessibilityLabel={_(msg`Go back`)}
accessibilityHint="">
<FontAwesomeIcon
size={24}
icon="angle-left"
style={pal.text as FontAwesomeIconStyle}
/>
</TouchableOpacity>
)
}
interface NavItemProps {
count?: string
href: string
@@ -224,12 +180,14 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
accessibilityLabel={_(msg`${count} unread items`)}
accessibilityHint=""
accessible={true}
numberOfLines={1}
style={[
a.absolute,
a.text_xs,
a.font_bold,
a.rounded_full,
a.text_center,
a.leading_tight,
{
top: '-10%',
left: count.length === 1 ? '50%' : '40%',
@@ -366,9 +324,9 @@ export function DesktopLeftNav() {
<View
role="navigation"
style={[
a.px_xl,
styles.leftNav,
isTablet && styles.leftNavTablet,
pal.view,
pal.border,
]}>
{hasSession ? (
@@ -381,8 +339,6 @@ export function DesktopLeftNav() {
{hasSession && (
<>
<BackBtn />
<NavItem
href="/"
icon={
@@ -525,8 +481,20 @@ const styles = StyleSheet.create({
position: 'fixed',
top: 10,
// @ts-ignore web only
left: 'calc(50vw - 300px - 220px - 20px)',
width: 220,
left: '50%',
transform: [
{
translateX: -300,
},
{
translateX: '-100%',
},
{
// @ts-ignore web only -esb
translateX: 'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
width: 240,
// @ts-ignore web only
maxHeight: 'calc(100vh - 10px)',
overflowY: 'auto',
@@ -538,7 +506,10 @@ const styles = StyleSheet.create({
borderRightWidth: 1,
height: '100%',
width: 76,
paddingLeft: 0,
paddingRight: 0,
alignItems: 'center',
transform: [],
},
profileCard: {
+11 -3
View File
@@ -28,7 +28,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
}
return (
<View style={[styles.rightNav, pal.view]}>
<View style={[a.px_xl, styles.rightNav]}>
<View style={{paddingVertical: 20}}>
{routeName === 'Search' ? (
<View style={{marginBottom: 18}}>
@@ -122,8 +122,16 @@ const styles = StyleSheet.create({
// @ts-ignore web only
position: 'fixed',
// @ts-ignore web only
left: 'calc(50vw + 300px + 20px)',
width: 300,
left: '50%',
transform: [
{
translateX: 300,
},
{
// @ts-ignore web only -esb
translateX: 'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
maxHeight: '100%',
overflowY: 'auto',
},
+27 -26
View File
@@ -3,10 +3,10 @@ import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {RemoveScroll} from 'react-remove-scroll'
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
import {colors} from '#/lib/styles'
@@ -34,7 +34,6 @@ function ShellInner() {
const {_} = useLingui()
const showDrawer = !isDesktop && isDrawerOpen
useWebBodyScrollLock(showDrawer)
useComposerKeyboardShortcut()
useIntentHandler()
@@ -58,31 +57,33 @@ function ShellInner() {
<PortalOutlet />
{showDrawer && (
<TouchableWithoutFeedback
onPress={ev => {
// Only close if press happens outside of the drawer
if (ev.target === ev.currentTarget) {
setDrawerOpen(false)
}
}}
accessibilityLabel={_(msg`Close navigation footer`)}
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
<View
style={[
styles.drawerMask,
{
backgroundColor: select(t.name, {
light: 'rgba(0, 57, 117, 0.1)',
dark: 'rgba(1, 82, 168, 0.1)',
dim: 'rgba(10, 13, 16, 0.8)',
}),
},
]}>
<View style={styles.drawerContainer}>
<DrawerContent />
<RemoveScroll>
<TouchableWithoutFeedback
onPress={ev => {
// Only close if press happens outside of the drawer
if (ev.target === ev.currentTarget) {
setDrawerOpen(false)
}
}}
accessibilityLabel={_(msg`Close navigation footer`)}
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
<View
style={[
styles.drawerMask,
{
backgroundColor: select(t.name, {
light: 'rgba(0, 57, 117, 0.1)',
dark: 'rgba(1, 82, 168, 0.1)',
dim: 'rgba(10, 13, 16, 0.8)',
}),
},
]}>
<View style={styles.drawerContainer}>
<DrawerContent />
</View>
</View>
</View>
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
</RemoveScroll>
)}
</>
)
+1 -1
View File
@@ -45,7 +45,6 @@
}
html {
background-color: white;
scrollbar-gutter: stable both-edges;
}
@media (prefers-color-scheme: dark) {
html {
@@ -84,6 +83,7 @@
/* We need this style to prevent web dropdowns from shifting the display when opening */
body {
width: 100%;
overflow-y: scroll;
}
</style>
</head>
+4 -29
View File
@@ -16374,7 +16374,7 @@ react-remove-scroll@2.5.5:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-remove-scroll@2.6.0:
react-remove-scroll@2.6.0, react-remove-scroll@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07"
integrity sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==
@@ -17621,16 +17621,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17730,7 +17721,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -17744,13 +17735,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -19073,7 +19057,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -19091,15 +19075,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"