[Layout] Base (#6907)

* Add common gutter styles as hook

* Add computed scrollbar gutter CSS vars

* Add new layout components

* Replace layout components in settings screens

* Remove old back button

* Invert web border logic for easier migration

* Clean up Slot API

* Port over FF handling of scrollbar offset

* Trade boilerplate for ease of use

* Limit to one line

* Allow two lines, fix wrapping

* Fix alignment

* sticky headers

* set max with on header and center

* [Layout] Notifications Header (#6910)

* Replace notifications screen header

* fix cropped indicator

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Replace Hashtag header (#6928)

* [Layout] ChatList header (#6929)

* Replace ChatList header

* update chat settings as well

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Add web borders to Chat settings

* Remove unused var

* Move ChatList header outside center

* Replace empty chat layout

* fix breakpoints

* [Layout] Scrollbar gutters (#6908)

* Fix sidebar alignment

* Make sure scrollbars don't hide

* Gift left nav more space

* Use stable one-edge, update logic in RightNav

* Ope

* Increase width

* Reset

* Add transform to sidebars

* Remove bg in sidebars

* Handle shifts in layout components

* Replace scroll-removal handling

* Make react-remove-scroll an explicit dep

* Remove unused script

* use correct scroll insets (#6950)

* [Layout] Feeds headers (#6913)

* Replace ViewHeader internals, duplicate old ViewHeader

* Replace Feeds header

* Replace SavedFeeds header

* Visual alignment

* Uglier but clear

* Use old ViewHeader for SavedFeeds

* use Layout.Center instead of Layout.Content

* use left-aligned header for feed edit

* delete unused old view header

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* [Layout] Every other screen (#6953)

* attempt to fix double borders on every other screen

* delete ListHeaderDesktop

* delete `SimpleViewHeader` and fix screens (#6956)

* Make Layout.Center not full height

* Refactor List to use Layout.Center, remove built-in borders

* Fix Home screen

* Refactor PagerWithHeader to use Layout components

* Replace components in ProfileFeed and ProfileList

* Borders on Profile

* Search screen replacements

* use new header for profile subpage header (#6958)

* Search AutocompleteResults

* use new header for starter pack wizard (#6957)

* Fix post thread

* Enable borders by default

* Moderation muted and blocked accounts

* Fix scrollbar offset on Labeler

* Remove ScrollView from Moderation

* Remove ScrollView from Deactivated

* Remove ScrollView from onboarding

* Remove ScrollView from SignupQueued

* Mark deprecations

* fix lint

* Fix double borders on profile load

* Remove unneeded CenteredView from noty Feed

* Remove double Center layout on Notifications screen

* Remove double Center layout on ChatList screen

* Handle scrollbar offset in chat

* Use new atom for other scrollbar offsets

* Remove borders from old views

* Better doc

* Remove temp migration prop

* Fix new atom usage on native

* Clean up Hashtag screen

* Layout docs

* Clarify usage in Pager

* Handle nested offset contexts

* Clean up Layout

* fix feeds page

* asymmetric header on native (#6969)

* Reusable header const

* Fix up home header

* Add back button to convo

* Add hitslop to header buttons

* Comment

* Better handling on native for new atom

* Format

* Fix nested flatlist on mod screens

* Use react-remove-scroll-bar directly

* Fix notification count overflow on web

* Clarify doc

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
Eric Bailey
2024-12-05 18:59:26 -06:00
committed by GitHub
parent 8467dfd452
commit 143e2c802d
89 changed files with 1725 additions and 2052 deletions
+21 -1
View File
@@ -1,7 +1,8 @@
import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native'
import * as tokens from '#/alf/tokens'
import {ios, native, web} from '#/alf/util/platform'
import {ios, native, platform, web} from '#/alf/util/platform'
import * as Layout from '#/components/Layout'
export const atoms = {
debug: {
@@ -21,6 +22,9 @@ export const atoms = {
relative: {
position: 'relative',
},
sticky: web({
position: 'sticky',
}),
inset_0: {
top: 0,
left: 0,
@@ -941,4 +945,20 @@ export const atoms = {
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
transitionDuration: '100ms',
}),
/**
* {@link Layout.SCROLLBAR_OFFSET}
*/
scrollbar_offset: platform({
web: {
transform: [
{
translateX: Layout.SCROLLBAR_OFFSET,
},
],
},
native: {
transform: [],
},
}) as {transform: Exclude<ViewStyle['transform'], string | undefined>},
} as const
+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])
}
+2
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 {RemoveScrollBar} from 'react-remove-scroll-bar'
import {logger} from '#/logger'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -103,6 +104,7 @@ export function Outer({
{isOpen && (
<Portal>
<Context.Provider value={context}>
<RemoveScrollBar />
<TouchableWithoutFeedback
accessibilityHint={undefined}
accessibilityLabel={_(msg`Close active dialog`)}
-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}
+199
View File
@@ -0,0 +1,199 @@
import {createContext, useCallback, useContext} from 'react'
import {GestureResponderEvent, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {HITSLOP_30} from '#/lib/constants'
import {NavigationProp} from '#/lib/routes/types'
import {isIOS} from '#/platform/detection'
import {useSetDrawerOpen} from '#/state/shell'
import {
atoms as a,
platform,
TextStyleProp,
useBreakpoints,
useGutterStyles,
useTheme,
} from '#/alf'
import {Button, ButtonIcon, ButtonProps} 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 {
BUTTON_VISUAL_ALIGNMENT_OFFSET,
HEADER_SLOT_SIZE,
} from '#/components/Layout/const'
import {ScrollbarOffsetContext} from '#/components/Layout/context'
import {Text} from '#/components/Typography'
export function Outer({
children,
noBottomBorder,
}: {
children: React.ReactNode
noBottomBorder?: boolean
}) {
const t = useTheme()
const gutter = useGutterStyles()
const {gtMobile} = useBreakpoints()
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
return (
<View
style={[
a.w_full,
!noBottomBorder && a.border_b,
a.flex_row,
a.align_center,
a.gap_sm,
gutter,
platform({
native: [a.pb_sm, a.pt_xs],
web: [a.py_sm],
}),
t.atoms.border_contrast_low,
gtMobile && [a.mx_auto, {maxWidth: 600}],
!isWithinOffsetView && a.scrollbar_offset,
]}>
{children}
</View>
)
}
const AlignmentContext = createContext<'platform' | 'left'>('platform')
export function Content({
children,
align = 'platform',
}: {
children?: React.ReactNode
align?: 'platform' | 'left'
}) {
return (
<View
style={[
a.flex_1,
a.justify_center,
isIOS && align === 'platform' && a.align_center,
{minHeight: HEADER_SLOT_SIZE},
]}>
<AlignmentContext.Provider value={align}>
{children}
</AlignmentContext.Provider>
</View>
)
}
export function Slot({children}: {children?: React.ReactNode}) {
return (
<View
style={[
a.z_50,
{
width: HEADER_SLOT_SIZE,
},
]}>
{children}
</View>
)
}
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const onPressBack = useCallback(
(evt: GestureResponderEvent) => {
onPress?.(evt)
if (evt.defaultPrevented) return
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
},
[onPress, navigation],
)
return (
<Slot>
<Button
label={_(msg`Go back`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
onPress={onPressBack}
hitSlop={HITSLOP_30}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}, style]}
{...props}>
<ButtonIcon icon={ArrowLeft} size="lg" />
</Button>
</Slot>
)
}
export function MenuButton() {
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const {gtMobile} = useBreakpoints()
const onPress = useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
return gtMobile ? null : (
<Slot>
<Button
label={_(msg`Open drawer menu`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
onPress={onPress}
hitSlop={HITSLOP_30}
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()
const align = useContext(AlignmentContext)
return (
<Text
style={[
a.text_lg,
a.font_heavy,
a.leading_tight,
isIOS && align === 'platform' && a.text_center,
gtMobile && a.text_xl,
style,
]}
numberOfLines={2}>
{children}
</Text>
)
}
export function SubtitleText({children}: {children: React.ReactNode}) {
const t = useTheme()
const align = useContext(AlignmentContext)
return (
<Text
style={[
a.text_sm,
a.leading_snug,
isIOS && align === 'platform' && a.text_center,
t.atoms.text_contrast_medium,
]}
numberOfLines={2}>
{children}
</Text>
)
}
+172
View File
@@ -0,0 +1,172 @@
# Layout
This directory contains our core layout components. Use these when creating new
screens, or when supplementing other components with functionality like
centering.
## Usage
If we aren't talking about the `shell` components, layouts on individual screens
look like more or less like this:
```tsx
<Outer>
<Header>...</Header>
<Content>...</Content>
</Outer>
```
I'll map these words to real components.
### `Layout.Screen`
Provides the "Outer" functionality for a screen, like taking up the full height
of the screen. **All screens should be wrapped with this component,** probably
as the outermost component.
> [!NOTE]
> On web, `Layout.Screen` also provides the side borders on our central content
> column. These borders are fixed position, 1px outside our center column width
> of 600px.
>
> What this effectively means is that _nothing inside the center content column
> needs (or should) define left/right borders._ That is now handled in one
> place: within `Layout.Screen`.
### `Layout.Header.*`
The `Layout.Header` component actually contains multiple sub-components. Use
this to compose different versions of the header. The most basic version looks
like this:
```tsx
<Layout.Header.Outer>
<Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}
<Layout.Header.Content>
<Layout.Header.TitleText>Account</Layout.Header.TitleText>
{/* Optional subtitle */}
<Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
```
Note the additional `Slot` component. This is here to keep the header balanced
and provide correct spacing on all platforms. The `Slot` is 34px wide, which
matches the `BackButton` and `MenuButton`.
> If anyone has better ideas, I'm all ears, but this was simple and the small
> amount of boilerplate is only incurred when creating a new screen, which is
> infrequent.
It can also function as a "slot" for a button positioned on the right side. See
the `Hashtag` screen for an example, abbreviated below:
```tsx
<Layout.Header.Slot>
<Button size='small' shape='round'>...</Button>
</Layout.Header.Slot>
```
If you need additional customization, simply use the components that are helpful
and create new ones as needed. A good example is the `SavedFeeds` screen, which
looks roughly like this:
```tsx
<Layout.Header.Outer>
<Layout.Header.BackButton />
{/* Override to align content to the left, making room for the button */}
<Layout.Header.Content align='left'>
<Layout.Header.TitleText>Edit My Feeds</Layout.Header.TitleText>
</Layout.Header.Content>
{/* Custom button, wider than 34px */}
<Button size='small'>...</Button>
</Layout.Header.Outer>
```
> [!TIP]
> The `Header` should be _outside_ the `Content` component in order to be
> fixed on scroll on native. Placing it inside will make it scroll with the rest
> of the page.
### `Layout.Content`
This provides the "Content" functionality for a screen. This component is
actually an `Animated.ScrollView`, and accepts props for that component. It
provides a little default styling as well. On web, it also _centers the content
inside our center content column of 600px_.
> [!NOTE]
> What about flatlists or pagers? Those components are not colocated here (yet).
> But those components serve the same purpose of "Content".
## Examples
The most basic layout available to us looks like this:
```tsx
<Layout.Screen>
<Layout.Header.Outer>
<Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}
<Layout.Header.Content>
<Layout.Header.TitleText>Account</Layout.Header.TitleText>
{/* Optional subtitle */}
<Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
...
</Layout.Content>
</Layout.Screen>
```
**For `List` views,** you'd sub in `List` for `Layout.Content` and it will
function the same. See `Feeds` screen for an example.
**For `Pager` views,** including `PagerWithHeader`, do the same. See `Hashtag`
screen for an example.
## Utilities
### `Layout.Center`
This component behaves like our old `CenteredView` component.
### `Layout.SCROLLBAR_OFFSET` and `Layout.SCROLLBAR_OFFSET_POSITIVE`
Provide a pre-configured CSS vars for use when aligning fixed position elements.
More on this below.
## Scrollbar gutter handling
Operating systems allow users to configure if their browser _always_ shows
scrollbars not. Some OSs also don't allow configuration.
The presence of scrollbars affects layout, particularly fixed position elements.
Browsers support `scrollbar-gutter`, but each behaves differently. Our approach
is to use the default `scrollbar-gutter: auto`. Basically, we start from a clean
slate.
This handling becomes particularly thorny when we need to lock scroll, like when
opening a dialog or dropdown. Radix uses the library `react-remove-scroll`
internally, which in turn depends on
[`react-remove-scroll-bar`](https://github.com/theKashey/react-remove-scroll-bar).
We've opted to rely on this transient dependency. This library adds some utility
classes and CSS vars to the page when scroll is locked.
**It is this CSS variable that we use in `SCROLLBAR_OFFSET` values.** This
ensures that elements do not shift relative to the screen when opening a
dropdown or dialog.
These styles are applied where needed and we should have very little need of
adjusting them often.
+16
View File
@@ -0,0 +1,16 @@
export const SCROLLBAR_OFFSET =
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)' as any
export const SCROLLBAR_OFFSET_POSITIVE =
'calc(var(--removed-body-scroll-bar-size, 0px) / 2)' as any
/**
* Useful for visually aligning icons within header buttons with the elements
* below them on the screen. Apply positively or negatively depending on side
* of the screen you're on.
*/
export const BUTTON_VISUAL_ALIGNMENT_OFFSET = 3
/**
* Corresponds to the width of a small square or round button
*/
export const HEADER_SLOT_SIZE = 34
+5
View File
@@ -0,0 +1,5 @@
import React from 'react'
export const ScrollbarOffsetContext = React.createContext({
isWithinOffsetView: false,
})
+188
View File
@@ -0,0 +1,188 @@
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,
useAnimatedProps,
} from 'react-native-reanimated'
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 {ScrollbarOffsetContext} from '#/components/Layout/context'
export * from '#/components/Layout/const'
export * as Header from '#/components/Layout/Header'
export type ScreenProps = React.ComponentProps<typeof View> & {
style?: StyleProp<ViewStyle>
}
/**
* Outermost component of every screen
*/
export const Screen = React.memo(function Screen({
style,
...props
}: ScreenProps) {
const {top} = useSafeAreaInsets()
return (
<>
{isWeb && <WebCenterBorders />}
<View
style={[a.util_screen_outer, {paddingTop: top}, style]}
{...props}
/>
</>
)
})
export type ContentProps = AnimatedScrollViewProps & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}
/**
* Default scroll view for simple pages
*/
export const Content = React.memo(function Content({
children,
style,
contentContainerStyle,
...props
}: ContentProps) {
const {footerHeight} = useShellLayout()
const animatedProps = useAnimatedProps(() => {
return {
scrollIndicatorInsets: {
bottom: footerHeight.get(),
top: 0,
right: 1,
},
} satisfies AnimatedScrollViewProps
})
return (
<Animated.ScrollView
id="content"
automaticallyAdjustsScrollIndicatorInsets={false}
// sets the scroll inset to the height of the footer
animatedProps={animatedProps}
style={[scrollViewStyles.common, style]}
contentContainerStyle={[
scrollViewStyles.contentContainer,
contentContainerStyle,
]}
{...props}>
{isWeb ? (
// @ts-ignore web only -esb
<Center>{children}</Center>
) : (
children
)}
</Animated.ScrollView>
)
})
const scrollViewStyles = StyleSheet.create({
common: {
width: '100%',
},
contentContainer: {
paddingBottom: 100,
},
})
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
children: React.ReactNode
contentContainerStyle?: StyleProp<ViewStyle>
}
/**
* Default scroll view for simple pages.
*
* BE SURE TO TEST THIS WHEN USING, it's untested as of writing this comment.
*/
export const KeyboardAwareContent = React.memo(function LayoutScrollView({
children,
style,
contentContainerStyle,
...props
}: KeyboardAwareContentProps) {
return (
<KeyboardAwareScrollView
style={[scrollViewStyles.common, style]}
contentContainerStyle={[
scrollViewStyles.contentContainer,
contentContainerStyle,
]}
keyboardShouldPersistTaps="handled"
{...props}>
{isWeb ? <Center>{children}</Center> : children}
</KeyboardAwareScrollView>
)
})
/**
* Utility component to center content within the screen
*/
export const Center = React.memo(function LayoutContent({
children,
style,
...props
}: ViewProps) {
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
const {gtMobile} = useBreakpoints()
const ctx = useMemo(() => ({isWithinOffsetView: true}), [])
return (
<View
style={[
a.w_full,
a.mx_auto,
gtMobile && {
maxWidth: 600,
},
style,
!isWithinOffsetView && a.scrollbar_offset,
]}
{...props}>
<ScrollbarOffsetContext.Provider value={ctx}>
{children}
</ScrollbarOffsetContext.Provider>
</View>
)
})
/**
* Only used within `Layout.Screen`, not for reuse
*/
const WebCenterBorders = React.memo(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%',
},
...a.scrollbar_offset.transform,
],
}),
]}
/>
) : null
})
+11 -2
View File
@@ -12,8 +12,14 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
function renderItem({item}: {item: GetLikes.Like}) {
return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
return (
<ProfileCardWithFollowBtn
key={item.actor.did}
profile={item.actor}
noBorder={index === 0}
/>
)
}
function keyExtractor(item: GetLikes.Like) {
@@ -81,6 +87,8 @@ export function LikedByList({uri}: {uri: string}) {
)}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
topBorder={false}
sideBorders={false}
/>
)
}
@@ -103,6 +111,7 @@ export function LikedByList({uri}: {uri: string}) {
onEndReachedThreshold={3}
initialNumToRender={initialNumToRender}
windowSize={11}
sideBorders={false}
/>
)
}
+1 -33
View File
@@ -109,38 +109,6 @@ function ListFooterMaybeError({
)
}
export function ListHeaderDesktop({
title,
subtitle,
}: {
title: string
subtitle?: string
}) {
const {gtTablet} = useBreakpoints()
const t = useTheme()
if (!gtTablet) return null
return (
<View
style={[
a.w_full,
a.py_sm,
a.px_xl,
a.gap_xs,
a.justify_center,
{minHeight: 50},
]}>
<Text style={[a.text_2xl, a.font_bold]}>{title}</Text>
{subtitle ? (
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
{subtitle}
</Text>
) : undefined}
</View>
)
}
let ListMaybePlaceholder = ({
isLoading,
noEmpty,
@@ -154,7 +122,7 @@ let ListMaybePlaceholder = ({
onGoBack,
hideBackButton,
sideBorders,
topBorder = true,
topBorder = false,
}: {
isLoading: boolean
noEmpty?: boolean
+17 -19
View File
@@ -65,25 +65,23 @@ export let MessagesListHeader = ({
a.pr_lg,
a.py_sm,
]}>
{!gtTablet && (
<TouchableOpacity
testID="conversationHeaderBackBtn"
onPress={onPressBack}
hitSlop={BACK_HITSLOP}
style={{width: 30, height: 30, marginTop: isWeb ? 6 : 4}}
accessibilityRole="button"
accessibilityLabel={_(msg`Back`)}
accessibilityHint="">
<FontAwesomeIcon
size={18}
icon="angle-left"
style={{
marginTop: 6,
}}
color={t.atoms.text.color}
/>
</TouchableOpacity>
)}
<TouchableOpacity
testID="conversationHeaderBackBtn"
onPress={onPressBack}
hitSlop={BACK_HITSLOP}
style={{width: 30, height: 30, marginTop: isWeb ? 6 : 4}}
accessibilityRole="button"
accessibilityLabel={_(msg`Back`)}
accessibilityHint="">
<FontAwesomeIcon
size={18}
icon="angle-left"
style={{
marginTop: 6,
}}
color={t.atoms.text.color}
/>
</TouchableOpacity>
{profile && moderation && blockInfo ? (
<HeaderReady
@@ -57,6 +57,7 @@ export function DateField({
open
timeZoneOffsetInMinutes={0}
theme={t.scheme}
// @ts-ignore TODO
buttonColor={t.name === 'light' ? '#000000' : '#ffffff'}
date={new Date(value)}
onConfirm={onChangeInternal}
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const FloppyDisk_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3 4a1 1 0 0 1 1-1h13a1 1 0 0 1 .707.293l3 3A1 1 0 0 1 21 7v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm6 15h6v-5H9v5Zm8 0v-6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v6H5V5h2v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5.414l2 2V19h-2ZM15 5H9v2h6V5Z',
})
-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()
})
}
+7 -14
View File
@@ -17,13 +17,13 @@ import {
} from '#/state/session'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {ScrollView} from '#/view/com/util/Views'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {AccountList} from '#/components/AccountList'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
@@ -104,24 +104,17 @@ export function Deactivated() {
}, [_, agent, setPending, setError, queryClient])
return (
<View style={[a.util_screen_outer, a.flex_1, t.atoms.bg]}>
<ScrollView
style={[
a.h_full,
a.w_full,
<View style={[a.util_screen_outer, a.flex_1]}>
<Layout.Content
contentContainerStyle={[
a.px_2xl,
{
paddingTop: isWeb ? 64 : insets.top + 16,
paddingBottom: isWeb ? 64 : insets.bottom,
},
]}
contentContainerStyle={[
a.w_full,
a.flex_row,
a.justify_center,
{borderWidth: 0},
]}>
<View style={[a.w_full, {maxWidth: COL_WIDTH}]}>
<View
style={[a.w_full, {marginHorizontal: 'auto', maxWidth: COL_WIDTH}]}>
<View style={[a.w_full, a.justify_center, a.align_center, a.pb_5xl]}>
<Logo width={40} />
</View>
@@ -218,7 +211,7 @@ export function Deactivated() {
</>
)}
</View>
</ScrollView>
</Layout.Content>
</View>
)
}
+30 -41
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,15 @@ 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 {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 {atoms as a, web} from '#/alf'
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'
@@ -110,46 +109,36 @@ 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
}
/>
</CenteredView>
<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>
<Pager
onPageSelected={onPageSelected}
renderTabBar={props => (
<CenteredView
sideBorders={true}
// @ts-ignore web only
style={
isWeb
? {
position: isWeb ? 'sticky' : '',
top: 0,
zIndex: 1,
}
: undefined
}>
<Layout.Center style={web([a.sticky, a.z_10, {top: 0}])}>
<TabBar items={sections.map(section => section.title)} {...props} />
</CenteredView>
</Layout.Center>
)}
initialPage={0}>
{sections.map((section, i) => (
+55 -105
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}
/>
)}
<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} />
@@ -238,14 +203,7 @@ export function MessagesScreen({navigation, route}: Props) {
return (
<Layout.Screen testID="messagesScreen">
{!gtMobile && (
<ViewHeader
title={_(msg`Messages`)}
renderButton={renderButton}
showBorder
canGoBack={false}
/>
)}
<Header newChatControl={newChatControl} />
<NewChat onNewChat={onNewChat} control={newChatControl} />
<List
data={conversations}
@@ -254,12 +212,6 @@ export function MessagesScreen({navigation, route}: Props) {
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
ListHeaderComponent={
<DesktopHeader
newChatControl={newChatControl}
onNavigateToSettings={onNavigateToSettings}
/>
}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
@@ -276,67 +228,65 @@ export function MessagesScreen({navigation, route}: Props) {
windowSize={11}
// @ts-ignore our .web version only -sfn
desktopFixedHeight
sideBorders={false}
/>
</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>
)
}
+4 -5
View File
@@ -17,7 +17,6 @@ import {useCurrentConvoId} from '#/state/messages/current-convo-id'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile'
import {useSetMinimalShellMode} from '#/state/shell'
import {CenteredView} from '#/view/com/util/Views'
import {MessagesList} from '#/screens/Messages/components/MessagesList'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
@@ -97,7 +96,7 @@ function Inner() {
if (convoState.status === ConvoStatus.Error) {
return (
<CenteredView style={[a.flex_1]} sideBorders>
<Layout.Center style={[a.flex_1]}>
<MessagesListHeader />
<Error
title={_(msg`Something went wrong`)}
@@ -105,12 +104,12 @@ function Inner() {
onRetry={() => convoState.error.retry()}
sideBorders={false}
/>
</CenteredView>
</Layout.Center>
)
}
return (
<CenteredView style={[a.flex_1]} sideBorders>
<Layout.Center style={[a.flex_1]}>
{!readyToShow && <MessagesListHeader />}
<View style={[a.flex_1]}>
{moderationOpts && recipient ? (
@@ -140,7 +139,7 @@ function Inner() {
</View>
)}
</View>
</CenteredView>
</Layout.Center>
)
}
+11 -5
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'
@@ -57,8 +55,16 @@ export function MessagesSettingsScreen({}: Props) {
return (
<Layout.Screen testID="messagesSettingsScreen">
<ScrollView stickyHeaderIndices={[0]}>
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
<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>
)
}
+13 -34
View File
@@ -1,6 +1,5 @@
import React from 'react'
import {Fragment, useCallback} from 'react'
import {Linking, View} from 'react-native'
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import {LABELS} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -19,8 +18,6 @@ import {
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
import {useSetMinimalShellMode} from '#/state/shell'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -37,6 +34,7 @@ import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Perso
import * as LabelingService from '#/components/LabelingServiceCard'
import * as Layout from '#/components/Layout'
import {InlineLinkText, Link} from '#/components/Link'
import {ListMaybePlaceholder} from '#/components/Lists'
import {Loader} from '#/components/Loader'
import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
import {Text} from '#/components/Typography'
@@ -75,35 +73,22 @@ function ErrorState({error}: {error: string}) {
export function ModerationScreen(
_props: NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>,
) {
const t = useTheme()
const {_} = useLingui()
const {
isLoading: isPreferencesLoading,
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
const {gtMobile} = useBreakpoints()
const {height} = useSafeAreaFrame()
const isLoading = isPreferencesLoading
const error = preferencesError
return (
<Layout.Screen testID="moderationScreen">
<CenteredView
testID="moderationScreen"
style={[
t.atoms.border_contrast_low,
t.atoms.bg,
{minHeight: height},
...(gtMobile ? [a.border_l, a.border_r] : []),
]}>
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
<Layout.Content>
{isLoading ? (
<View style={[a.w_full, a.align_center, a.pt_2xl]}>
<Loader size="xl" fill={t.atoms.text.color} />
</View>
<ListMaybePlaceholder isLoading={true} sideBorders={false} />
) : error || !preferences ? (
<ErrorState
error={
@@ -114,7 +99,7 @@ export function ModerationScreen(
) : (
<ModerationScreenInner preferences={preferences} />
)}
</CenteredView>
</Layout.Content>
</Layout.Screen>
)
}
@@ -169,7 +154,7 @@ export function ModerationScreenInner({
} = useMyLabelersQuery()
useFocusEffect(
React.useCallback(() => {
useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
@@ -183,7 +168,7 @@ export function ModerationScreenInner({
const ageNotSet = !preferences.userAge
const isUnderage = (preferences.userAge || 0) < 18
const onToggleAdultContentEnabled = React.useCallback(
const onToggleAdultContentEnabled = useCallback(
async (selected: boolean) => {
try {
await setAdultContentPref({
@@ -201,13 +186,7 @@ export function ModerationScreenInner({
const disabledOnIOS = isIOS && !adultContentEnabled
return (
<ScrollView
contentContainerStyle={[
a.border_0,
a.pt_2xl,
a.px_lg,
gtMobile && a.px_2xl,
]}>
<View style={[a.pt_2xl, a.px_lg, gtMobile && a.px_2xl]}>
<Text
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
<Trans>Moderation tools</Trans>
@@ -420,7 +399,7 @@ export function ModerationScreenInner({
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
{labelers.map((labeler, i) => {
return (
<React.Fragment key={labeler.creator.did}>
<Fragment key={labeler.creator.did}>
{i !== 0 && <Divider />}
<LabelingService.Link labeler={labeler}>
{state => (
@@ -457,12 +436,12 @@ export function ModerationScreenInner({
</LabelingService.Outer>
)}
</LabelingService.Link>
</React.Fragment>
</Fragment>
)
})}
</View>
)}
<View style={{height: 200}} />
</ScrollView>
<View style={{height: 150}} />
</View>
)
}
+2 -4
View File
@@ -1,13 +1,11 @@
import React from 'react'
import {View} from 'react-native'
import Animated from 'react-native-reanimated'
import {ScrollView, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isWeb} from '#/platform/detection'
import {useOnboardingDispatch} from '#/state/shell'
import {ScrollView} from '#/view/com/util/Views'
import {Context} from '#/screens/Onboarding/state'
import {
atoms as a,
@@ -36,7 +34,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
const {gtMobile} = useBreakpoints()
const onboardDispatch = useOnboardingDispatch()
const {state, dispatch} = React.useContext(Context)
const scrollview = React.useRef<Animated.ScrollView>(null)
const scrollview = React.useRef<ScrollView>(null)
const prevActiveStep = React.useRef<string>(state.activeStep)
React.useEffect(() => {
+2 -8
View File
@@ -5,13 +5,10 @@ import {useFocusEffect} from '@react-navigation/native'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {isWeb} from '#/platform/detection'
import {useSetMinimalShellMode} from '#/state/shell'
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
export const PostLikedByScreen = ({route}: Props) => {
@@ -28,11 +25,8 @@ export const PostLikedByScreen = ({route}: Props) => {
return (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ListHeaderDesktop title={_(msg`Liked By`)} />
<ViewHeader title={_(msg`Liked By`)} showBorder={!isWeb} />
<PostLikedByComponent uri={uri} />
</CenteredView>
<ViewHeader title={_(msg`Liked By`)} />
<PostLikedByComponent uri={uri} />
</Layout.Screen>
)
}
-2
View File
@@ -11,7 +11,6 @@ import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuot
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostQuotes'>
export const PostQuotesScreen = ({route}: Props) => {
@@ -29,7 +28,6 @@ export const PostQuotesScreen = ({route}: Props) => {
return (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ListHeaderDesktop title={_(msg`Quotes`)} />
<ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} />
<PostQuotesComponent uri={uri} />
</CenteredView>
-2
View File
@@ -11,7 +11,6 @@ import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
export const PostRepostedByScreen = ({route}: Props) => {
@@ -29,7 +28,6 @@ export const PostRepostedByScreen = ({route}: Props) => {
return (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ListHeaderDesktop title={_(msg`Reposted By`)} />
<ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} />
<PostRepostedByComponent uri={uri} />
</CenteredView>
+19 -10
View File
@@ -15,14 +15,22 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import * as Layout from '#/components/Layout'
import {
ListFooter,
ListHeaderDesktop,
ListMaybePlaceholder,
} from '#/components/Lists'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
function renderItem({item}: {item: AppBskyActorDefs.ProfileViewBasic}) {
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
function renderItem({
item,
index,
}: {
item: AppBskyActorDefs.ProfileViewBasic
index: number
}) {
return (
<ProfileCardWithFollowBtn
key={item.did}
profile={item}
noBorder={index === 0}
/>
)
}
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
@@ -93,6 +101,7 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
if (followers.length < 1) {
return (
<Layout.Screen>
<ViewHeader title={_(msg`Followers you know`)} />
<ListMaybePlaceholder
isLoading={isDidLoading || isFollowersLoading}
isError={isError}
@@ -100,6 +109,8 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
topBorder={false}
sideBorders={false}
/>
</Layout.Screen>
)
@@ -116,9 +127,6 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
ListHeaderComponent={
<ListHeaderDesktop title={_(msg`Followers you know`)} />
}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
@@ -130,6 +138,7 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
desktopFixedHeight
initialNumToRender={initialNumToRender}
windowSize={11}
sideBorders={false}
/>
</Layout.Screen>
)
+4 -3
View File
@@ -15,10 +15,11 @@ import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {isNative} from '#/platform/detection'
import {ListRef} from '#/view/com/util/List'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
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'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import {LabelerLabelPreference} from '#/components/moderation/LabelPreference'
import {Text} from '#/components/Typography'
@@ -75,7 +76,7 @@ export const ProfileLabelsSection = React.forwardRef<
}, [isFocused, scrollElRef, setScrollViewTag])
return (
<CenteredView style={{flex: 1, minHeight}} sideBorders>
<Layout.Center style={{flex: 1, minHeight}}>
{isLabelerLoading ? (
<View style={[a.w_full, a.align_center]}>
<Loader size="xl" />
@@ -95,7 +96,7 @@ export const ProfileLabelsSection = React.forwardRef<
headerHeight={headerHeight}
/>
)}
</CenteredView>
</Layout.Center>
)
})
+9 -1
View File
@@ -21,7 +21,15 @@ export function AboutSettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`About`)} />
<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
@@ -39,7 +39,15 @@ export function AccessibilitySettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Accessibility`)} />
<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]}>
+9 -1
View File
@@ -38,7 +38,15 @@ export function AccountSettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Account`)} />
<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>
+10 -2
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'
@@ -20,7 +20,15 @@ export function AppIconSettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_('App Icon')} />
<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>
+9 -1
View File
@@ -44,7 +44,15 @@ export function AppPasswordsScreen({}: Props) {
return (
<Layout.Screen testID="AppPasswordsScreen">
<Layout.Header title={_(msg`App Passwords`)} />
<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
+9 -1
View File
@@ -79,7 +79,15 @@ export function AppearanceSettingsScreen({}: Props) {
return (
<LayoutAnimationConfig skipExiting skipEntering>
<Layout.Screen testID="preferencesThreadsScreen">
<Layout.Header title={_(msg`Appearance`)} />
<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
@@ -32,7 +32,15 @@ export function ContentAndMediaSettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Content and Media`)} />
<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,17 @@ type Props = NativeStackScreenProps<
'PreferencesExternalEmbeds'
>
export function ExternalMediaPreferencesScreen({}: Props) {
const {_} = useLingui()
return (
<Layout.Screen testID="externalMediaPreferencesScreen">
<Layout.Header title={_(msg`External Media Preferences`)} />
<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>
@@ -46,7 +46,15 @@ export function FollowingFeedPreferencesScreen({}: Props) {
return (
<Layout.Screen testID="followingFeedPreferencesScreen">
<Layout.Header title={_(msg`Following Feed Preferences`)} />
<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>
+9 -1
View File
@@ -64,7 +64,15 @@ export function LanguageSettingsScreen({}: Props) {
return (
<Layout.Screen testID="PreferencesLanguagesScreen">
<Layout.Header title={_(msg`Languages`)} />
<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}>
@@ -33,7 +33,15 @@ export function NotificationSettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Notification Settings`)} />
<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
@@ -29,7 +29,15 @@ export function PrivacyAndSecuritySettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Privacy and Security`)} />
<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>
+9 -1
View File
@@ -73,7 +73,15 @@ export function SettingsScreen({}: Props) {
return (
<Layout.Screen>
<Layout.Header title={_(msg`Settings`)} />
<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
+9 -1
View File
@@ -38,7 +38,15 @@ export function ThreadPreferencesScreen({}: Props) {
return (
<Layout.Screen testID="threadPreferencesScreen">
<Layout.Header title={_(msg`Thread Preferences`)} />
<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>
+1 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {Modal, View} from 'react-native'
import {Modal, ScrollView, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {StatusBar} from 'expo-status-bar'
import {msg, plural, Trans} from '@lingui/macro'
@@ -9,7 +9,6 @@ import {logger} from '#/logger'
import {isIOS, isWeb} from '#/platform/detection'
import {isSignupQueued, useAgent, useSessionApi} from '#/state/session'
import {useOnboardingDispatch} from '#/state/shell'
import {ScrollView} from '#/view/com/util/Views'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+29 -52
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {Keyboard, TouchableOpacity, View} from 'react-native'
import {Keyboard, View} from 'react-native'
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Image} from 'expo-image'
@@ -10,13 +10,12 @@ import {
ModerationOpts,
} from '@atproto/api'
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
@@ -29,7 +28,7 @@ import {
parseStarterPackUri,
} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {isAndroid, isNative, isWeb} from '#/platform/detection'
import {isNative} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAllListMembersQuery} from '#/state/queries/list-members'
import {useProfileQuery} from '#/state/queries/profile'
@@ -147,7 +146,6 @@ function WizardInner({
}) {
const navigation = useNavigation<NavigationProp>()
const {_} = useLingui()
const t = useTheme()
const setMinimalShellMode = useSetMinimalShellMode()
const [state, dispatch] = useWizardState()
const {currentAccount} = useSession()
@@ -283,45 +281,24 @@ function WizardInner({
return (
<CenteredView style={[a.flex_1]} sideBorders>
<View
style={[
a.flex_row,
a.pb_sm,
a.px_md,
a.border_b,
t.atoms.border_contrast_medium,
a.gap_sm,
a.justify_between,
a.align_center,
isAndroid && a.pt_sm,
isWeb && [a.py_md],
]}>
<View style={[{width: 65}]}>
<TouchableOpacity
testID="viewHeaderDrawerBtn"
hitSlop={HITSLOP_10}
accessibilityRole="button"
accessibilityLabel={_(msg`Back`)}
accessibilityHint={_(msg`Go back to the previous step`)}
onPress={() => {
if (state.currentStep === 'Details') {
navigation.pop()
} else {
dispatch({type: 'Back'})
}
}}>
<FontAwesomeIcon
size={18}
icon="angle-left"
color={t.atoms.text.color}
/>
</TouchableOpacity>
</View>
<Text style={[a.flex_1, a.font_bold, a.text_lg, a.text_center]}>
{currUiStrings.header}
</Text>
<View style={[{width: 65}]} />
</View>
<Layout.Header.Outer>
<Layout.Header.BackButton
label={_(msg`Back`)}
accessibilityHint={_(msg`Go back to the previous step`)}
onPress={evt => {
if (state.currentStep !== 'Details') {
evt.preventDefault()
dispatch({type: 'Back'})
}
}}
/>
<Layout.Header.Content>
<Layout.Header.TitleText>
{currUiStrings.header}
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Container>
{state.currentStep === 'Details' ? (
@@ -463,17 +440,17 @@ function Footer({
<Trans>
<Text style={[a.font_bold, textStyles]}>You</Text> and
<Text> </Text>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[1] /* [0] is self, skip it */)}{' '}
</Text>
are included in your starter pack
</Trans>
) : items.length > 2 ? (
<Trans context="profiles">
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[1] /* [0] is self, skip it */)},{' '}
</Text>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[2])},{' '}
</Text>
and{' '}
@@ -504,29 +481,29 @@ function Footer({
{
items.length === 1 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[0])}
</Text>{' '}
is included in your starter pack
</Trans>
) : items.length === 2 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[0])}
</Text>{' '}
and
<Text> </Text>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[1])}{' '}
</Text>
are included in your starter pack
</Trans>
) : items.length > 2 ? (
<Trans context="feeds">
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[0])},{' '}
</Text>
<Text style={[a.font_bold, textStyles]}>
<Text style={[a.font_bold, textStyles]} emoji>
{getName(items[1])},{' '}
</Text>
and{' '}
+1 -1
View File
@@ -108,7 +108,7 @@ export function FeedPage({
}, [scrollToTop, feed, queryClient, setHasNew])
return (
<View testID={testID} style={s.h100pct}>
<View testID={testID}>
<MainScrollProvider>
<FeedFeedbackProvider value={feedFeedback}>
<Feed
+36 -90
View File
@@ -1,26 +1,27 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {View} from 'react-native'
import Animated from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useSession} from '#/state/session'
import {useShellLayout} from '#/state/shell/shell-layout'
import {HomeHeaderLayoutMobile} from '#/view/com/home/HomeHeaderLayoutMobile'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, useBreakpoints, useGutterStyles, useTheme} from '#/alf'
import {ButtonIcon} from '#/components/Button'
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
import * as Layout from '#/components/Layout'
import {Link} from '#/components/Link'
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
export function HomeHeaderLayout(props: {
children: React.ReactNode
tabBarAnchor: JSX.Element | null | undefined
}) {
const {isMobile} = useWebMediaQueries()
if (isMobile) {
const {gtMobile} = useBreakpoints()
if (!gtMobile) {
return <HomeHeaderLayoutMobile {...props} />
} else {
return <HomeHeaderLayoutDesktopAndTablet {...props} />
@@ -40,98 +41,43 @@ function HomeHeaderLayoutDesktopAndTablet({
const {hasSession} = useSession()
const {_} = useLingui()
const kawaii = useKawaiiMode()
const gutter = useGutterStyles()
return (
<>
{hasSession && (
<View
style={[
a.relative,
a.flex_row,
a.justify_end,
a.align_center,
a.pt_lg,
a.px_md,
a.pb_2xs,
t.atoms.bg,
t.atoms.border_contrast_low,
styles.bar,
kawaii && {paddingTop: 22, paddingBottom: 16},
]}>
<Layout.Center>
<View
style={[
a.absolute,
a.inset_0,
a.pt_lg,
a.m_auto,
kawaii && {paddingTop: 4, paddingBottom: 0},
{
width: kawaii ? 84 : 28,
},
]}>
<Logo width={kawaii ? 60 : 28} />
style={[a.flex_row, a.align_center, a.pt_md, gutter, t.atoms.bg]}>
<View style={{width: 34}} />
<View style={[a.flex_1, a.align_center, a.justify_center]}>
<Logo width={kawaii ? 60 : 28} />
</View>
<Link
to="/feeds"
hitSlop={10}
label={_(msg`View your feeds and explore more`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
style={[a.justify_center]}>
<ButtonIcon icon={FeedsIcon} size="lg" />
</Link>
</View>
<Link
to="/feeds"
hitSlop={10}
label={_(msg`View your feeds and explore more`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
style={[
a.justify_center,
{
marginTop: -4,
},
]}>
<FeedsIcon size="md" fill={t.atoms.text_contrast_medium.color} />
</Link>
</View>
</Layout.Center>
)}
{tabBarAnchor}
<Animated.View
onLayout={e => {
headerHeight.set(e.nativeEvent.layout.height)
}}
style={[
t.atoms.bg,
t.atoms.border_contrast_low,
styles.bar,
styles.tabBar,
headerMinimalShellTransform,
]}>
{children}
</Animated.View>
<Layout.Center
style={[a.sticky, a.z_10, a.align_center, t.atoms.bg, {top: 0}]}>
<Animated.View
onLayout={e => {
headerHeight.set(e.nativeEvent.layout.height)
}}
style={[headerMinimalShellTransform]}>
{children}
</Animated.View>
</Layout.Center>
</>
)
}
const styles = StyleSheet.create({
bar: {
// @ts-ignore Web only
left: 'calc(50% - 300px)',
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
},
topBar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 18,
paddingTop: 16,
paddingBottom: 8,
},
tabBar: {
// @ts-ignore Web only
position: 'sticky',
top: 0,
flexDirection: 'column',
alignItems: 'center',
borderLeftWidth: 1,
borderRightWidth: 1,
zIndex: 1,
},
})
+44 -80
View File
@@ -1,25 +1,22 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {View} from 'react-native'
import Animated from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {HITSLOP_10} from '#/lib/constants'
import {PressableScale} from '#/lib/custom-animations/PressableScale'
import {useHaptics} from '#/lib/haptics'
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
import {usePalette} from '#/lib/hooks/usePalette'
import {isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session'
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
import {useShellLayout} from '#/state/shell/shell-layout'
import {Logo} from '#/view/icons/Logo'
import {atoms} from '#/alf'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
import {atoms as a, useTheme} from '#/alf'
import {ButtonIcon} from '#/components/Button'
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import * as Layout from '#/components/Layout'
import {Link} from '#/components/Link'
import {IS_DEV} from '#/env'
export function HomeHeaderLayoutMobile({
children,
@@ -28,58 +25,50 @@ export function HomeHeaderLayoutMobile({
tabBarAnchor: JSX.Element | null | undefined
}) {
const t = useTheme()
const pal = usePalette('default')
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const {headerHeight} = useShellLayout()
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
const {hasSession} = useSession()
const onPressAvi = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
const playHaptic = useHaptics()
return (
<Animated.View
style={[pal.border, styles.tabBar, headerMinimalShellTransform]}
style={[
a.fixed,
a.z_10,
t.atoms.bg,
{
top: 0,
left: 0,
right: 0,
},
headerMinimalShellTransform,
]}
onLayout={e => {
headerHeight.set(e.nativeEvent.layout.height)
}}>
<View style={[pal.view, styles.topBar]}>
<View style={[{width: 100}]}>
<TouchableOpacity
testID="viewHeaderDrawerBtn"
onPress={onPressAvi}
accessibilityRole="button"
accessibilityLabel={_(msg`Open navigation`)}
accessibilityHint={_(
msg`Access profile and other navigation links`,
)}
hitSlop={HITSLOP_10}>
<Menu size="lg" fill={t.atoms.text_contrast_medium.color} />
</TouchableOpacity>
<Layout.Header.Outer noBottomBorder>
<Layout.Header.Slot>
<Layout.Header.MenuButton />
</Layout.Header.Slot>
<View style={[a.flex_1, a.align_center]}>
<PressableScale
targetScale={0.9}
onPress={() => {
emitSoftReset()
}}
onPressIn={() => {
playHaptic('Heavy')
}}
onPressOut={() => {
playHaptic('Light')
}}>
<Logo width={30} />
</PressableScale>
</View>
<View>
<Logo width={30} />
</View>
<View
style={[
atoms.flex_row,
atoms.justify_end,
atoms.align_center,
atoms.gap_md,
{width: 100},
]}>
{IS_DEV && (
<>
<Link
label="View storybook"
to="/sys/debug"
testID="storybookBtn">
<ColorPalette size="md" />
</Link>
</>
)}
<Layout.Header.Slot>
{hasSession && (
<Link
testID="viewHeaderHomeFeedPrefsBtn"
@@ -93,40 +82,15 @@ export function HomeHeaderLayoutMobile({
style={[
a.justify_center,
{
marginTop: 2,
marginRight: -6,
marginRight: -Layout.BUTTON_VISUAL_ALIGNMENT_OFFSET,
},
]}>
<FeedsIcon size="lg" fill={t.atoms.text_contrast_medium.color} />
<ButtonIcon icon={FeedsIcon} size="lg" />
</Link>
)}
</View>
</View>
</Layout.Header.Slot>
</Layout.Header.Outer>
{children}
</Animated.View>
)
}
const styles = StyleSheet.create({
tabBar: {
// @ts-ignore web-only
position: isWeb ? 'fixed' : 'absolute',
zIndex: 1,
left: 0,
right: 0,
top: 0,
flexDirection: 'column',
},
topBar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 5,
width: '100%',
minHeight: 46,
},
title: {
fontSize: 21,
},
})
+9 -7
View File
@@ -15,8 +15,8 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
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,14 @@ export function Lightbox() {
const initialIndex = activeLightbox.index
const imgs = activeLightbox.images
return (
<LightboxInner
imgs={imgs}
initialIndex={initialIndex}
onClose={closeLightbox}
/>
<>
<RemoveScrollBar />
<LightboxInner
imgs={imgs}
initialIndex={initialIndex}
onClose={closeLightbox}
/>
</>
)
}
+2 -5
View File
@@ -15,7 +15,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {cleanError} from '#/lib/strings/errors'
import {s} from '#/lib/styles'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
import {EmptyState} from '#/view/com/util/EmptyState'
@@ -110,7 +109,7 @@ export function MyLists({
) : (
<View
style={[
(index !== 0 || isWeb) && a.border_t,
index !== 0 && a.border_t,
t.atoms.border_contrast_low,
a.px_lg,
a.py_lg,
@@ -141,8 +140,6 @@ export function MyLists({
}
contentContainerStyle={[s.contentContainer]}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
/>
)}
</View>
@@ -160,8 +157,8 @@ export function MyLists({
onRefresh={onRefresh}
contentContainerStyle={[s.contentContainer]}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
sideBorders={false}
/>
)}
</View>
+2 -2
View File
@@ -1,8 +1,8 @@
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
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,7 +22,6 @@ import * as VerifyEmailModal from './VerifyEmail'
export function ModalsContainer() {
const {isModalActive, activeModals} = useModals()
useWebBodyScrollLock(isModalActive)
if (!isModalActive) {
return null
@@ -30,6 +29,7 @@ export function ModalsContainer() {
return (
<>
<RemoveScrollBar />
{activeModals.map((modal, i) => (
<Modal key={`modal-${i}`} modal={modal} />
))}
+7 -16
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'
@@ -22,7 +21,6 @@ import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {List, ListRef} from '#/view/com/util/List'
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {CenteredView} from '#/view/com/util/Views'
import {FeedItem} from './FeedItem'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@@ -46,7 +44,6 @@ export function Feed({
const [isPTRing, setIsPTRing] = React.useState(false)
const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
@@ -133,11 +130,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 +139,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(
@@ -168,12 +161,10 @@ export function Feed({
return (
<View style={s.hContentRegion}>
{error && (
<CenteredView>
<ErrorMessage
message={cleanError(error)}
onPressTryAgain={onPressTryAgain}
/>
</CenteredView>
<ErrorMessage
message={cleanError(error)}
onPressTryAgain={onPressTryAgain}
/>
)}
<List
testID="notifsFeed"
+11 -49
View File
@@ -1,10 +1,10 @@
import * as React from 'react'
import {ScrollView, StyleSheet, View} from 'react-native'
import {ScrollView, View} from 'react-native'
import {useAnimatedRef} from 'react-native-reanimated'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {atoms as a, web} from '#/alf'
import * as Layout from '#/components/Layout'
import {ListMethods} from '../util/List'
import {TabBar} from './TabBar'
@@ -121,30 +121,19 @@ let PagerTabBar = ({
onSelect?: (index: number) => void
tabBarAnchor?: JSX.Element | null | undefined
}): React.ReactNode => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<>
<View
style={[
!isMobile && styles.headerContainerDesktop,
pal.border,
!isHeaderReady && styles.loadingHeader,
]}>
{renderHeader?.()}
</View>
<Layout.Center>{renderHeader?.()}</Layout.Center>
{tabBarAnchor}
<View
style={[
styles.tabBarContainer,
isMobile
? styles.tabBarContainerMobile
: styles.tabBarContainerDesktop,
pal.border,
<Layout.Center
style={web([
a.sticky,
a.z_10,
{
top: 0,
display: isHeaderReady ? undefined : 'none',
},
]}>
])}>
<TabBar
testID={testID}
items={items}
@@ -154,7 +143,7 @@ let PagerTabBar = ({
dragProgress={undefined as any /* native-only */}
dragState={undefined as any /* native-only */}
/>
</View>
</Layout.Center>
</>
)
}
@@ -180,33 +169,6 @@ function PagerItem({
})
}
const styles = StyleSheet.create({
headerContainerDesktop: {
marginHorizontal: 'auto',
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
},
tabBarContainer: {
// @ts-ignore web-only
position: 'sticky',
top: 0,
zIndex: 1,
},
tabBarContainerDesktop: {
marginHorizontal: 'auto',
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
},
tabBarContainerMobile: {
paddingHorizontal: 0,
},
loadingHeader: {
borderColor: 'transparent',
},
})
function toArray<T>(v: T | T[]): T[] {
if (Array.isArray(v)) {
return v
+2 -3
View File
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
@@ -18,7 +17,7 @@ function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
<ProfileCardWithFollowBtn
key={item.actor.did}
profile={item.actor}
noBorder={index === 0 && !isWeb}
noBorder={index === 0}
/>
)
}
@@ -88,6 +87,7 @@ export function PostLikedBy({uri}: {uri: string}) {
)}
errorMessage={cleanError(resolveError || error)}
sideBorders={false}
topBorder={false}
/>
)
}
@@ -108,7 +108,6 @@ export function PostLikedBy({uri}: {uri: string}) {
onRetry={fetchNextPage}
/>
}
// @ts-ignore our .web version only -prf
desktopFixedHeight
initialNumToRender={initialNumToRender}
windowSize={11}
+1 -2
View File
@@ -11,7 +11,6 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {usePostQuotesQuery} from '#/state/queries/post-quotes'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
@@ -30,7 +29,7 @@ function renderItem({
}
index: number
}) {
return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} />
return <Post post={item.post} hideTopBorder={index === 0} />
}
function keyExtractor(item: {
+14 -2
View File
@@ -12,8 +12,20 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
function renderItem({
item,
index,
}: {
item: ActorDefs.ProfileViewBasic
index: number
}) {
return (
<ProfileCardWithFollowBtn
key={item.did}
profile={item}
noBorder={index === 0}
/>
)
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
+2 -3
View File
@@ -32,7 +32,6 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {useComposerControls} from '#/state/shell'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {Text} from '#/components/Typography'
@@ -484,7 +483,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
}
return (
<CenteredView style={[a.flex_1]} sideBorders={true}>
<>
{showHeader && (
<ViewHeader
title={_(msg({message: `Post`, context: 'description'}))}
@@ -531,7 +530,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
{isMobile && canReply && hasSession && (
<MobileComposePrompt onPressReply={onPressReply} />
)}
</CenteredView>
</>
)
}
+1 -2
View File
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useSession} from '#/state/session'
@@ -25,7 +24,7 @@ function renderItem({
<ProfileCardWithFollowBtn
key={item.did}
profile={item}
noBorder={index === 0 && !isWeb}
noBorder={index === 0}
/>
)
}
+1 -2
View File
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useSession} from '#/state/session'
@@ -25,7 +24,7 @@ function renderItem({
<ProfileCardWithFollowBtn
key={item.did}
profile={item}
noBorder={index === 0 && !isWeb}
noBorder={index === 0}
/>
)
}
+18 -85
View File
@@ -1,29 +1,24 @@
import React from 'react'
import {Pressable, StyleSheet, View} from 'react-native'
import {Pressable, View} from 'react-native'
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {BACK_HITSLOP} from '#/lib/constants'
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {makeProfileLink} from '#/lib/routes/links'
import {NavigationProp} from '#/lib/routes/types'
import {sanitizeHandle} from '#/lib/strings/handles'
import {isNative} from '#/platform/detection'
import {emitSoftReset} from '#/state/events'
import {useLightboxControls} from '#/state/lightbox'
import {useSetDrawerOpen} from '#/state/shell'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {TextLink} from '#/view/com/util/Link'
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {Text} from '#/view/com/util/text/Text'
import {UserAvatar, UserAvatarType} from '#/view/com/util/UserAvatar'
import {StarterPack} from '#/components/icons/StarterPack'
import {TextLink} from '../util/Link'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {Text} from '../util/text/Text'
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
import {CenteredView} from '../util/Views'
import * as Layout from '#/components/Layout'
export function ProfileSubpageHeader({
isLoading,
@@ -48,7 +43,6 @@ export function ProfileSubpageHeader({
| undefined
avatarType: UserAvatarType | 'starter-pack'
}>) {
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
@@ -57,18 +51,6 @@ export function ProfileSubpageHeader({
const canGoBack = navigation.canGoBack()
const aviRef = useHandleRef()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
const onPressMenu = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
const _openLightbox = React.useCallback(
(uri: string, thumbRect: MeasuredDimensions | null) => {
openLightbox({
@@ -106,42 +88,17 @@ export function ProfileSubpageHeader({
}, [_openLightbox, avatar, aviRef])
return (
<CenteredView style={pal.view}>
{isMobile && (
<View
style={[
{
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
paddingTop: isNative ? 0 : 8,
paddingBottom: 8,
paddingHorizontal: isMobile ? 12 : 14,
},
pal.border,
]}>
<Pressable
testID="headerDrawerBtn"
onPress={canGoBack ? onPressBack : onPressMenu}
hitSlop={BACK_HITSLOP}
style={canGoBack ? styles.backBtn : styles.backBtnWide}
accessibilityRole="button"
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
accessibilityHint="">
{canGoBack ? (
<FontAwesomeIcon
size={18}
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : (
<Menu size="lg" style={[{marginTop: 4}, pal.textLight]} />
)}
</Pressable>
<View style={{flex: 1}} />
{children}
</View>
)}
<>
<Layout.Header.Outer>
{canGoBack ? (
<Layout.Header.BackButton />
) : (
<Layout.Header.MenuButton />
)}
<Layout.Header.Content />
{children}
</Layout.Header.Outer>
<View
style={{
flexDirection: 'row',
@@ -206,31 +163,7 @@ export function ProfileSubpageHeader({
</Text>
)}
</View>
{!isMobile && (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
{children}
</View>
)}
</View>
</CenteredView>
</>
)
}
const styles = StyleSheet.create({
backBtn: {
width: 20,
height: 30,
},
backBtnWide: {
width: 20,
height: 30,
marginRight: 4,
},
backIcon: {
marginTop: 6,
},
})
+49 -66
View File
@@ -4,10 +4,9 @@ import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook
import {batchedUpdates} from '#/lib/batchedUpdates'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {addStyle} from '#/lib/styles'
import * as Layout from '#/components/Layout'
export type ListMethods = any // TODO: Better types.
export type ListProps<ItemT> = Omit<
@@ -24,6 +23,9 @@ export type ListProps<ItemT> = Omit<
desktopFixedHeight?: number | boolean
// Web only prop to contain the scroll to the container rather than the window
disableFullWindowScroll?: boolean
/**
* @deprecated Should be using Layout components
*/
sideBorders?: boolean
}
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
@@ -56,20 +58,11 @@ function ListImpl<ItemT>(
renderItem,
extraData,
style,
sideBorders = true,
...props
}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const contextScrollHandlers = useScrollHandlers()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!isMobile) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerScroll,
)
}
const isEmpty = !data || data.length === 0
@@ -326,53 +319,53 @@ function ListImpl<ItemT>(
styles.parentTreeVisibilityDetector
}
/>
<View
ref={containerRef}
style={[
!isMobile && sideBorders && styles.sideBorders,
contentContainerStyle,
desktopFixedHeight ? styles.minHeightViewport : null,
pal.border,
]}>
<Visibility
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={handleAboveTheFoldVisibleChange}
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
/>
{onStartReached && !isEmpty && (
<EdgeVisibility
<Layout.Center>
<View
ref={containerRef}
style={[
contentContainerStyle,
desktopFixedHeight ? styles.minHeightViewport : null,
]}>
<Visibility
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onHeadVisibilityChange}
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
containerRef={containerRef}
onVisibleChange={handleAboveTheFoldVisibleChange}
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
/>
)}
{headerComponent}
{isEmpty
? emptyComponent
: (data as Array<ItemT>)?.map((item, index) => {
const key = keyExtractor!(item, index)
return (
<Row<ItemT>
key={key}
item={item}
index={index}
renderItem={renderItem}
extraData={extraData}
onItemSeen={onItemSeen}
/>
)
})}
{onEndReached && !isEmpty && (
<EdgeVisibility
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onTailVisibilityChange}
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
containerRef={containerRef}
/>
)}
{footerComponent}
</View>
{onStartReached && !isEmpty && (
<EdgeVisibility
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onHeadVisibilityChange}
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
containerRef={containerRef}
/>
)}
{headerComponent}
{isEmpty
? emptyComponent
: (data as Array<ItemT>)?.map((item, index) => {
const key = keyExtractor!(item, index)
return (
<Row<ItemT>
key={key}
item={item}
index={index}
renderItem={renderItem}
extraData={extraData}
onItemSeen={onItemSeen}
/>
)
})}
{onEndReached && !isEmpty && (
<EdgeVisibility
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onTailVisibilityChange}
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
containerRef={containerRef}
/>
)}
{footerComponent}
</View>
</Layout.Center>
</View>
)
}
@@ -558,16 +551,6 @@ export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
// https://stackoverflow.com/questions/7944460/detect-safari-browser
const styles = StyleSheet.create({
sideBorders: {
borderLeftWidth: 1,
borderRightWidth: 1,
},
containerScroll: {
width: '100%',
maxWidth: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
minHeightViewport: {
// @ts-ignore web only
minHeight: '100vh',
+6 -3
View File
@@ -1,14 +1,17 @@
import {ActivityIndicator, View} from 'react-native'
import {s} from '#/lib/styles'
import {CenteredView} from './Views'
import * as Layout from '#/components/Layout'
/**
* @deprecated use Layout compoenents directly
*/
export function LoadingScreen() {
return (
<CenteredView>
<Layout.Content>
<View style={s.p20}>
<ActivityIndicator size="large" />
</View>
</CenteredView>
</Layout.Content>
)
}
-114
View File
@@ -1,114 +0,0 @@
import React from 'react'
import {
StyleProp,
StyleSheet,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
import {isWeb} from '#/platform/detection'
import {useSetDrawerOpen} from '#/state/shell'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {CenteredView} from './Views'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
export function SimpleViewHeader({
showBackButton = true,
style,
children,
}: React.PropsWithChildren<{
showBackButton?: boolean
style?: StyleProp<ViewStyle>
}>) {
const pal = usePalette('default')
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries()
const canGoBack = navigation.canGoBack()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
const onPressMenu = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
const Container = isMobile ? View : CenteredView
return (
<Container
style={[
styles.header,
isMobile && styles.headerMobile,
isWeb && styles.headerWeb,
pal.view,
style,
]}>
{showBackButton ? (
<TouchableOpacity
testID="viewHeaderDrawerBtn"
onPress={canGoBack ? onPressBack : onPressMenu}
hitSlop={BACK_HITSLOP}
style={canGoBack ? styles.backBtn : styles.backBtnWide}
accessibilityRole="button"
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
accessibilityHint="">
{canGoBack ? (
<FontAwesomeIcon
size={18}
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : (
<Menu size="lg" style={[{marginTop: 4}, pal.textLight]} />
)}
</TouchableOpacity>
) : null}
{children}
</Container>
)
}
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 12,
width: '100%',
},
headerMobile: {
paddingHorizontal: 12,
paddingVertical: 10,
},
headerWeb: {
// @ts-ignore web-only
position: 'sticky',
top: 0,
zIndex: 1,
},
backBtn: {
width: 30,
height: 30,
},
backBtnWide: {
width: 30,
height: 30,
paddingLeft: 4,
marginRight: 4,
},
backIcon: {
marginTop: 6,
},
})
+13 -257
View File
@@ -1,271 +1,27 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import Animated from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
import {useSetDrawerOpen} from '#/state/shell'
import {useTheme} from '#/alf'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {Text} from './text/Text'
import {CenteredView} from './Views'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
import {Header} from '#/components/Layout'
/**
* Legacy ViewHeader component. Use Layout.Header going forward.
*
* @deprecated
*/
export function ViewHeader({
title,
subtitle,
canGoBack,
showBackButton = true,
hideOnScroll,
showOnDesktop,
showBorder,
renderButton,
}: {
title: string
subtitle?: string
canGoBack?: boolean
showBackButton?: boolean
hideOnScroll?: boolean
showOnDesktop?: boolean
showBorder?: boolean
renderButton?: () => JSX.Element
}) {
const pal = usePalette('default')
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {isDesktop, isTablet} = useWebMediaQueries()
const t = useTheme()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
const onPressMenu = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
if (isDesktop) {
if (showOnDesktop) {
return (
<DesktopWebHeader
title={title}
subtitle={subtitle}
renderButton={renderButton}
showBorder={showBorder}
/>
)
}
return null
} else {
if (typeof canGoBack === 'undefined') {
canGoBack = navigation.canGoBack()
}
return (
<Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
<View style={{flex: 1}}>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
{showBackButton ? (
<TouchableOpacity
testID="viewHeaderDrawerBtn"
onPress={canGoBack ? onPressBack : onPressMenu}
hitSlop={BACK_HITSLOP}
style={canGoBack ? styles.backBtn : styles.backBtnWide}
accessibilityRole="button"
accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)}
accessibilityHint={
canGoBack ? '' : _(msg`Access navigation links and settings`)
}>
{canGoBack ? (
<FontAwesomeIcon
size={18}
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : !isTablet ? (
<Menu size="lg" style={[{marginTop: 3}, pal.textLight]} />
) : null}
</TouchableOpacity>
) : null}
<View style={styles.titleContainer} pointerEvents="none">
<Text emoji type="title" style={[pal.text, styles.title]}>
{title}
</Text>
</View>
{renderButton ? (
renderButton()
) : showBackButton ? (
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
) : null}
</View>
{subtitle ? (
<View
style={[styles.titleContainer, {marginTop: -3}]}
pointerEvents="none">
<Text
style={[
pal.text,
styles.subtitle,
t.atoms.text_contrast_medium,
]}>
{subtitle}
</Text>
</View>
) : undefined}
</View>
</Container>
)
}
}
function DesktopWebHeader({
title,
subtitle,
renderButton,
showBorder = true,
}: {
title: string
subtitle?: string
renderButton?: () => JSX.Element
showBorder?: boolean
}) {
const pal = usePalette('default')
const t = useTheme()
return (
<CenteredView
style={[
styles.header,
styles.desktopHeader,
pal.border,
{
borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0,
},
{display: 'flex', flexDirection: 'column'},
]}>
<View>
<View style={styles.titleContainer} pointerEvents="none">
<Text type="title-lg" style={[pal.text, styles.title]}>
{title}
</Text>
</View>
{renderButton?.()}
</View>
{subtitle ? (
<View>
<View style={[styles.titleContainer]} pointerEvents="none">
<Text
style={[
pal.text,
styles.subtitleDesktop,
t.atoms.text_contrast_medium,
]}>
{subtitle}
</Text>
</View>
</View>
) : null}
</CenteredView>
<Header.Outer>
<Header.BackButton />
<Header.Content>
<Header.TitleText>{title}</Header.TitleText>
</Header.Content>
<Header.Slot>{renderButton?.() ?? null}</Header.Slot>
</Header.Outer>
)
}
function Container({
children,
hideOnScroll,
showBorder,
}: {
children: React.ReactNode
hideOnScroll: boolean
showBorder?: boolean
}) {
const pal = usePalette('default')
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
if (!hideOnScroll) {
return (
<View
style={[
styles.header,
pal.view,
pal.border,
showBorder && styles.border,
]}>
{children}
</View>
)
}
return (
<Animated.View
style={[
styles.header,
styles.headerFloating,
pal.view,
pal.border,
headerMinimalShellTransform,
showBorder && styles.border,
]}>
{children}
</Animated.View>
)
}
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
paddingHorizontal: 12,
paddingVertical: 6,
width: '100%',
},
headerFloating: {
position: 'absolute',
top: 0,
width: '100%',
},
desktopHeader: {
paddingVertical: 12,
maxWidth: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
border: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
titleContainer: {
marginLeft: 'auto',
marginRight: 'auto',
alignItems: 'center',
},
title: {
fontWeight: '600',
},
subtitle: {
fontSize: 13,
},
subtitleDesktop: {
fontSize: 15,
},
backBtn: {
width: 30,
height: 30,
},
backBtnWide: {
width: 30,
height: 30,
paddingLeft: 4,
marginRight: 4,
},
backIcon: {
marginTop: 6,
},
})
+7
View File
@@ -15,9 +15,16 @@ export type FlatList_INTERNAL<ItemT = any> = Omit<
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
'CellRendererComponent'
>
/**
* @deprecated use `Layout` components
*/
export const ScrollView = Animated.ScrollView
export type ScrollView = typeof Animated.ScrollView
/**
* @deprecated use `Layout` components
*/
export const CenteredView = forwardRef<
View,
React.PropsWithChildren<
+8 -23
View File
@@ -31,10 +31,12 @@ interface AddedProps {
desktopFixedHeight?: boolean | number
}
/**
* @deprecated use `Layout` components
*/
export const CenteredView = React.forwardRef(function CenteredView(
{
style,
sideBorders,
topBorder,
...props
}: React.PropsWithChildren<
@@ -47,13 +49,6 @@ export const CenteredView = React.forwardRef(function CenteredView(
if (!isMobile) {
style = addStyle(style, styles.container)
}
if (sideBorders && !isMobile) {
style = addStyle(style, {
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
})
style = addStyle(style, pal.border)
}
if (topBorder) {
style = addStyle(style, {
borderTopWidth: 1,
@@ -75,7 +70,6 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
>,
ref: React.Ref<FlatList<ItemT>>,
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!isMobile) {
contentContainerStyle = addStyle(
@@ -123,11 +117,7 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
return (
<Animated.FlatList
ref={ref}
contentContainerStyle={[
styles.contentContainer,
contentContainerStyle,
pal.border,
]}
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
style={style}
contentOffset={contentOffset}
{...props}
@@ -135,12 +125,13 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
)
})
/**
* @deprecated use `Layout` components
*/
export const ScrollView = React.forwardRef(function ScrollViewImpl(
{contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>,
ref: React.Ref<Animated.ScrollView>,
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!isMobile) {
contentContainerStyle = addStyle(
@@ -150,11 +141,7 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
}
return (
<Animated.ScrollView
contentContainerStyle={[
styles.contentContainer,
contentContainerStyle,
pal.border,
]}
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
// @ts-ignore something is wrong with the reanimated types -prf
ref={ref}
{...props}
@@ -164,8 +151,6 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
const styles = StyleSheet.create({
contentContainer: {
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
// @ts-ignore web only
minHeight: '100vh',
},
+3 -1
View File
@@ -36,7 +36,9 @@ export function ErrorScreen({
return (
<>
{showHeader && isMobile && <ViewHeader title="Error" showBorder />}
{showHeader && isMobile && (
<ViewHeader title={_(msg`Error`)} showBorder />
)}
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
<View style={styles.errorIconContainer}>
<View
+45 -83
View File
@@ -24,14 +24,13 @@ import {useSetMinimalShellMode} from '#/state/shell'
import {useComposerControls} from '#/state/shell/composer'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {FAB} from '#/view/com/util/fab/FAB'
import {TextLink} from '#/view/com/util/Link'
import {List, ListMethods} from '#/view/com/util/List'
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {Text} from '#/view/com/util/text/Text'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
import {atoms as a, useTheme} from '#/alf'
import {ButtonIcon} from '#/components/Button'
import {Divider} from '#/components/Divider'
import * as FeedCard from '#/components/FeedCard'
import {SearchInput} from '#/components/forms/SearchInput'
@@ -40,7 +39,9 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components
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'
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
import * as Layout from '#/components/Layout'
import {Link} from '#/components/Link'
import * as ListCard from '#/components/ListCard'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
@@ -102,7 +103,7 @@ type FlatlistSlice =
export function FeedsScreen(_props: Props) {
const pal = usePalette('default')
const {openComposer} = useComposerControls()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
const {isMobile} = useWebMediaQueries()
const [query, setQuery] = React.useState('')
const [isPTR, setIsPTR] = React.useState(false)
const {
@@ -374,22 +375,6 @@ export function FeedsScreen(_props: Props) {
isUserSearching,
])
const renderHeaderBtn = React.useCallback(() => {
return (
<View style={styles.headerBtnGroup}>
<TextLink
testID="editFeedsBtn"
type="lg-medium"
href="/settings/saved-feeds"
accessibilityLabel={_(msg`Edit My Feeds`)}
accessibilityHint=""
text={_(msg`Edit`)}
style={[pal.link, a.pr_xs]}
/>
</View>
)
}, [pal, _])
const searchBarIndex = items.findIndex(
item => item.type === 'popularFeedsHeader',
)
@@ -430,36 +415,7 @@ export function FeedsScreen(_props: Props) {
</View>
)
} else if (item.type === 'savedFeedsHeader') {
return (
<>
{!isMobile && (
<View
style={[
pal.view,
styles.header,
pal.border,
{
borderBottomWidth: 1,
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
<Trans>Feeds</Trans>
</Text>
<View style={styles.headerBtnGroup}>
<TextLink
type="lg"
href="/settings/saved-feeds"
accessibilityLabel={_(msg`Edit My Feeds`)}
accessibilityHint=""
text={_(msg`Edit`)}
style={[pal.link]}
/>
</View>
</View>
)}
<FeedsSavedHeader />
</>
)
return <FeedsSavedHeader />
} else if (item.type === 'savedFeedNoResults') {
return (
<View
@@ -530,13 +486,8 @@ export function FeedsScreen(_props: Props) {
return null
},
[
isMobile,
pal.view,
pal.border,
pal.text,
pal.textLight,
pal.link,
_,
query,
onChangeQuery,
onPressCancelSearch,
@@ -547,31 +498,45 @@ export function FeedsScreen(_props: Props) {
return (
<Layout.Screen testID="FeedsScreen">
{isMobile && (
<ViewHeader
title={_(msg`Feeds`)}
renderButton={renderHeaderBtn}
showBorder
/>
)}
<Layout.Center>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Feeds</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot>
<Link
testID="editFeedsBtn"
to="/settings/saved-feeds"
label={_(msg`Edit My Feeds`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
style={[a.justify_center, {right: -3}]}>
<ButtonIcon icon={Gear} size="lg" />
</Link>
</Layout.Header.Slot>
</Layout.Header.Outer>
<List
ref={listRef}
style={[!isTabletOrDesktop && s.flex1, styles.list]}
data={items}
keyExtractor={item => item.key}
contentContainerStyle={styles.contentContainer}
renderItem={renderItem}
refreshing={isPTR}
onRefresh={isUserSearching ? undefined : onPullToRefresh}
initialNumToRender={10}
onEndReached={onEndReached}
// @ts-ignore our .web version only -prf
desktopFixedHeight
scrollIndicatorInsets={{right: 1}}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
/>
<List
ref={listRef}
data={items}
keyExtractor={item => item.key}
contentContainerStyle={styles.contentContainer}
renderItem={renderItem}
refreshing={isPTR}
onRefresh={isUserSearching ? undefined : onPullToRefresh}
initialNumToRender={10}
onEndReached={onEndReached}
desktopFixedHeight
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
sideBorders={false}
/>
</Layout.Center>
{hasSession && (
<FAB
@@ -728,7 +693,7 @@ function FeedsSavedHeader() {
}>
<IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
<Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
<Trans>My Feeds</Trans>
</Text>
<Text style={[t.atoms.text_contrast_high]}>
@@ -754,7 +719,7 @@ function FeedsAboutHeader() {
size="lg"
/>
<View style={[a.flex_1, a.gap_sm]}>
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
<Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
<Trans>Discover New Feeds</Trans>
</Text>
<Text style={[t.atoms.text_contrast_high]}>
@@ -769,9 +734,6 @@ function FeedsAboutHeader() {
}
const styles = StyleSheet.create({
list: {
height: '100%',
},
contentContainer: {
paddingBottom: 100,
},
+26 -46
View File
@@ -1,33 +1,26 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {useEmail} from '#/lib/hooks/useEmail'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useModalControls} from '#/state/modals'
import {useSetMinimalShellMode} from '#/state/shell'
import {MyLists} from '#/view/com/lists/MyLists'
import {Button} from '#/view/com/util/forms/Button'
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
import {Text} from '#/view/com/util/text/Text'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export function ListsScreen({}: Props) {
const {_} = useLingui()
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const {needsEmailVerification} = useEmail()
@@ -62,43 +55,30 @@ export function ListsScreen({}: Props) {
return (
<Layout.Screen testID="listsScreen">
<SimpleViewHeader
showBackButton={isMobile}
style={[
pal.border,
isMobile
? {borderBottomWidth: StyleSheet.hairlineWidth}
: {
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
},
]}>
<View style={{flex: 1}}>
<Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
<Trans>User Lists</Trans>
</Text>
<Text style={pal.textLight}>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content align="left">
<Layout.Header.TitleText>
<Trans>Lists</Trans>
</Layout.Header.TitleText>
<Layout.Header.SubtitleText>
<Trans>Public, shareable lists which can drive feeds.</Trans>
</Text>
</View>
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
<Button
testID="newUserListBtn"
type="default"
onPress={onPressNewList}
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 8,
}}>
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
<Text type="button" style={pal.text}>
<Trans context="action">New</Trans>
</Text>
</Button>
</View>
</SimpleViewHeader>
<MyLists filter="curate" style={s.flexGrow1} />
</Layout.Header.SubtitleText>
</Layout.Header.Content>
<Button
label={_(msg`New list`)}
testID="newUserListBtn"
color="secondary"
variant="solid"
size="small"
onPress={onPressNewList}>
<ButtonIcon icon={PlusIcon} />
<ButtonText>
<Trans context="action">New</Trans>
</ButtonText>
</Button>
</Layout.Header.Outer>
<MyLists filter="curate" style={a.flex_grow} />
<VerifyEmailDialog
reasonText={_(
msg`Before creating a list, you must first verify your email.`,
+6 -20
View File
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {Text} from '#/view/com/util/text/Text'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<
@@ -97,14 +96,7 @@ export function ModerationBlockedAccounts({}: Props) {
)
return (
<Layout.Screen testID="blockedAccountsScreen">
<CenteredView
style={[
styles.container,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
testID="blockedAccountsScreen">
<Layout.Center>
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
<Text
type="sm"
@@ -112,6 +104,9 @@ export function ModerationBlockedAccounts({}: Props) {
styles.description,
pal.text,
isTabletOrDesktop && styles.descriptionDesktop,
{
marginTop: 20,
},
]}>
<Trans>
Blocked accounts cannot reply in your threads, mention you, or
@@ -120,7 +115,7 @@ export function ModerationBlockedAccounts({}: Props) {
</Trans>
</Text>
{isEmpty ? (
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
<View style={[pal.border]}>
{isError ? (
<ErrorScreen
title="Oops!"
@@ -166,21 +161,12 @@ export function ModerationBlockedAccounts({}: Props) {
desktopFixedHeight
/>
)}
</CenteredView>
</Layout.Center>
</Layout.Screen>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
title: {
textAlign: 'center',
marginTop: 12,
+25 -39
View File
@@ -1,33 +1,26 @@
import React from 'react'
import {View} from 'react-native'
import {AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {useEmail} from '#/lib/hooks/useEmail'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useModalControls} from '#/state/modals'
import {useSetMinimalShellMode} from '#/state/shell'
import {MyLists} from '#/view/com/lists/MyLists'
import {Button} from '#/view/com/util/forms/Button'
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
import {Text} from '#/view/com/util/text/Text'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
export function ModerationModlistsScreen({}: Props) {
const {_} = useLingui()
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const {needsEmailVerification} = useEmail()
@@ -62,39 +55,32 @@ export function ModerationModlistsScreen({}: Props) {
return (
<Layout.Screen testID="moderationModlistsScreen">
<SimpleViewHeader
showBackButton={isMobile}
style={
!isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}]
}>
<View style={{flex: 1}}>
<Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content align="left">
<Layout.Header.TitleText>
<Trans>Moderation Lists</Trans>
</Text>
<Text style={pal.textLight}>
</Layout.Header.TitleText>
<Layout.Header.SubtitleText>
<Trans>
Public, shareable lists of users to mute or block in bulk.
</Trans>
</Text>
</View>
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
<Button
testID="newModListBtn"
type="default"
onPress={onPressNewList}
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 8,
}}>
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
<Text type="button" style={pal.text}>
<Trans>New</Trans>
</Text>
</Button>
</View>
</SimpleViewHeader>
<MyLists filter="mod" style={s.flexGrow1} />
</Layout.Header.SubtitleText>
</Layout.Header.Content>
<Button
label={_(msg`New list`)}
testID="newModListBtn"
color="secondary"
variant="solid"
size="small"
onPress={onPressNewList}>
<ButtonIcon icon={PlusIcon} />
<ButtonText>
<Trans context="action">New</Trans>
</ButtonText>
</Button>
</Layout.Header.Outer>
<MyLists filter="mod" style={a.flex_grow} />
<VerifyEmailDialog
reasonText={_(
msg`Before creating a list, you must first verify your email.`,
+7 -21
View File
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {Text} from '#/view/com/util/text/Text'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<
@@ -97,21 +96,17 @@ export function ModerationMutedAccounts({}: Props) {
)
return (
<Layout.Screen testID="mutedAccountsScreen">
<CenteredView
style={[
styles.container,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
testID="mutedAccountsScreen">
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
<Layout.Center>
<Text
type="sm"
style={[
styles.description,
pal.text,
isTabletOrDesktop && styles.descriptionDesktop,
{
marginTop: 20,
},
]}>
<Trans>
Muted accounts have their posts removed from your feed and from your
@@ -119,7 +114,7 @@ export function ModerationMutedAccounts({}: Props) {
</Trans>
</Text>
{isEmpty ? (
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
<View style={[pal.border]}>
{isError ? (
<ErrorScreen
title="Oops!"
@@ -165,21 +160,12 @@ export function ModerationMutedAccounts({}: Props) {
desktopFixedHeight
/>
)}
</CenteredView>
</Layout.Center>
</Layout.Screen>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
title: {
textAlign: 'center',
marginTop: 12,
+65 -113
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,121 +106,77 @@ 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,
]}>
return (
<Layout.Screen testID="notificationsScreen">
<Layout.Header.Outer>
<Layout.Header.MenuButton />
<Layout.Header.Content>
<Button
label={_(msg`Notifications`)}
accessibilityHint={_(msg`Refresh notifications`)}
onPress={emitSoftReset}>
{({hovered, pressed}) => (
<Text
style={[
a.text_2xl,
a.font_bold,
(hovered || pressed) && a.underline,
]}>
onPress={emitSoftReset}
style={[a.justify_start]}>
{({hovered}) => (
<Layout.Header.TitleText
style={[a.w_full, hovered && a.underline]}>
<Trans>Notifications</Trans>
{hasNew && (
{isWeb && gtTablet && hasNew && (
<View
style={{
left: 4,
top: -8,
backgroundColor: t.palette.primary_500,
width: 8,
height: 8,
borderRadius: 4,
}}
style={[
a.rounded_full,
{
width: 8,
height: 8,
bottom: 3,
left: 6,
backgroundColor: t.palette.primary_500,
},
]}
/>
)}
</Text>
</Layout.Header.TitleText>
)}
</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])
</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>
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}
<MainScrollProvider>
<Feed
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef}
overridePriorityNotifications={params?.show === 'all'}
/>
<MainScrollProvider>
<Feed
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef}
ListHeaderComponent={ListHeaderComponent}
overridePriorityNotifications={params?.show === 'all'}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onPressLoadLatest}
label={_(msg`Load new notifications`)}
showIndicator={hasNew}
/>
)}
<FAB
testID="composeFAB"
onPress={() => openComposer({})}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
</MainScrollProvider>
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onPressLoadLatest}
label={_(msg`Load new notifications`)}
showIndicator={hasNew}
/>
</CenteredView>
)}
<FAB
testID="composeFAB"
onPress={() => openComposer({})}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</Layout.Screen>
)
}
+1 -5
View File
@@ -1,12 +1,10 @@
import React from 'react'
import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
import {atoms as a} from '#/alf'
import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
@@ -24,9 +22,7 @@ export function PostThreadScreen({route}: Props) {
return (
<Layout.Screen testID="postThreadScreen">
<View style={a.flex_1}>
<PostThreadComponent uri={uri} />
</View>
<PostThreadComponent uri={uri} />
</Layout.Screen>
)
}
+2 -4
View File
@@ -40,11 +40,9 @@ import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {FAB} from '#/view/com/util/fab/FAB'
import {ListRef} from '#/view/com/util/List'
import {CenteredView} from '#/view/com/util/Views'
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
import {web} from '#/alf'
import * as Layout from '#/components/Layout'
import {ScreenHider} from '#/components/moderation/ScreenHider'
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
@@ -116,9 +114,9 @@ function ProfileScreenInner({route}: Props) {
// Most pushes will happen here, since we will have only placeholder data
if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
return (
<CenteredView sideBorders style={web({height: '100vh'})}>
<Layout.Content>
<ProfileHeaderLoading />
</CenteredView>
</Layout.Content>
)
}
if (resolveError || profileError) {
+4 -5
View File
@@ -49,7 +49,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
import {Text} from '#/view/com/util/text/Text'
import * as Toast from '#/view/com/util/Toast'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {Button as NewButton, ButtonText} from '#/components/Button'
import {useRichText} from '#/components/hooks/useRichText'
@@ -98,7 +97,7 @@ export function ProfileFeedScreen(props: Props) {
if (error) {
return (
<Layout.Screen testID="profileFeedScreenError">
<CenteredView>
<Layout.Content>
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
<Text type="title-lg" style={[pal.text, s.mb10]}>
<Trans>Could not load feed</Trans>
@@ -120,7 +119,7 @@ export function ProfileFeedScreen(props: Props) {
</Button>
</View>
</View>
</CenteredView>
</Layout.Content>
</Layout.Screen>
)
}
@@ -394,7 +393,7 @@ export function ProfileFeedScreenInner({
])
return (
<View style={s.hContentRegion}>
<>
<ReportDialog
control={reportDialogControl}
params={{
@@ -434,7 +433,7 @@ export function ProfileFeedScreenInner({
accessibilityHint=""
/>
)}
</View>
</>
)
}
-2
View File
@@ -10,7 +10,6 @@ import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
export const ProfileFollowersScreen = ({route}: Props) => {
@@ -27,7 +26,6 @@ export const ProfileFollowersScreen = ({route}: Props) => {
return (
<Layout.Screen testID="profileFollowersScreen">
<CenteredView sideBorders={true}>
<ListHeaderDesktop title={_(msg`Followers`)} />
<ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
<ProfileFollowersComponent name={name} />
</CenteredView>
-2
View File
@@ -10,7 +10,6 @@ import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/Prof
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
export const ProfileFollowsScreen = ({route}: Props) => {
@@ -27,7 +26,6 @@ export const ProfileFollowsScreen = ({route}: Props) => {
return (
<Layout.Screen testID="profileFollowsScreen">
<CenteredView sideBorders={true}>
<ListHeaderDesktop title={_(msg`Following`)} />
<ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
<ProfileFollowsComponent name={name} />
</CenteredView>
+4 -6
View File
@@ -69,7 +69,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
import {Text} from '#/view/com/util/text/Text'
import * as Toast from '#/view/com/util/Toast'
import {CenteredView} from '#/view/com/util/Views'
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
import {atoms as a, useTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
@@ -107,20 +106,20 @@ function ProfileListScreenInner(props: Props) {
if (resolveError) {
return (
<CenteredView>
<Layout.Content>
<ErrorScreen
error={_(
msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
)}
/>
</CenteredView>
</Layout.Content>
)
}
if (listError) {
return (
<CenteredView>
<Layout.Content>
<ErrorScreen error={cleanError(listError)} />
</CenteredView>
</Layout.Content>
)
}
@@ -1010,7 +1009,6 @@ function ErrorScreen({error}: {error: string}) {
pal.view,
pal.border,
{
marginTop: 10,
paddingHorizontal: 18,
paddingVertical: 14,
borderTopWidth: StyleSheet.hairlineWidth,
+117 -127
View File
@@ -25,13 +25,12 @@ import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
import {TextLink} from '#/view/com/util/Link'
import {Text} from '#/view/com/util/text/Text'
import * as Toast from '#/view/com/util/Toast'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
@@ -51,7 +50,7 @@ function SavedFeedsInner({
}) {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile, isTabletOrDesktop, isDesktop} = useWebMediaQueries()
const {isMobile, isDesktop} = useWebMediaQueries()
const setMinimalShellMode = useSetMinimalShellMode()
const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
useOverwriteSavedFeedsMutation()
@@ -88,136 +87,128 @@ function SavedFeedsInner({
}
}, [_, overwriteSavedFeeds, currentFeeds, navigation])
const renderHeaderBtn = React.useCallback(() => {
return (
<Button
size="small"
variant={hasUnsavedChanges ? 'solid' : 'solid'}
color={hasUnsavedChanges ? 'primary' : 'secondary'}
onPress={onSaveChanges}
label={_(msg`Save changes`)}
disabled={isOverwritePending || !hasUnsavedChanges}
style={[isDesktop && a.mt_sm]}
testID="saveChangesBtn">
<ButtonText style={[isDesktop && a.text_md]}>
{isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
</ButtonText>
{isOverwritePending && <ButtonIcon icon={Loader} />}
</Button>
)
}, [_, isDesktop, onSaveChanges, hasUnsavedChanges, isOverwritePending])
return (
<Layout.Screen>
<CenteredView
style={[a.util_screen_outer]}
sideBorders={isTabletOrDesktop}>
<ViewHeader
title={_(msg`Edit My Feeds`)}
showOnDesktop
showBorder
renderButton={renderHeaderBtn}
/>
<ScrollView style={[a.flex_1]} contentContainerStyle={[a.border_0]}>
{noSavedFeedsOfAnyType && (
<View style={[pal.border, a.border_b]}>
<NoSavedFeedsOfAnyType />
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content align="left">
<Layout.Header.TitleText>
<Trans>Feeds</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Button
testID="saveChangesBtn"
size="small"
variant={hasUnsavedChanges ? 'solid' : 'solid'}
color={hasUnsavedChanges ? 'primary' : 'secondary'}
onPress={onSaveChanges}
label={_(msg`Save changes`)}
disabled={isOverwritePending || !hasUnsavedChanges}>
<ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} />
<ButtonText>
{isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
</ButtonText>
</Button>
</Layout.Header.Outer>
<Layout.Content>
{noSavedFeedsOfAnyType && (
<View style={[pal.border, a.border_b]}>
<NoSavedFeedsOfAnyType />
</View>
)}
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
<Trans>Pinned Feeds</Trans>
</Text>
</View>
{preferences ? (
!pinnedFeeds.length ? (
<View
style={[
pal.border,
isMobile && s.flex1,
pal.viewLight,
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
<Trans>You don't have any pinned feeds.</Trans>
</Text>
</View>
)}
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
<Trans>Pinned Feeds</Trans>
</Text>
</View>
{preferences ? (
!pinnedFeeds.length ? (
<View
style={[
pal.border,
isMobile && s.flex1,
pal.viewLight,
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
<Trans>You don't have any pinned feeds.</Trans>
</Text>
</View>
) : (
pinnedFeeds.map(f => (
<ListItem
key={f.id}
feed={f}
isPinned
currentFeeds={currentFeeds}
setCurrentFeeds={setCurrentFeeds}
preferences={preferences}
/>
))
)
) : (
<ActivityIndicator style={{marginTop: 20}} />
)}
pinnedFeeds.map(f => (
<ListItem
key={f.id}
feed={f}
isPinned
currentFeeds={currentFeeds}
setCurrentFeeds={setCurrentFeeds}
preferences={preferences}
/>
))
)
) : (
<ActivityIndicator style={{marginTop: 20}} />
)}
{noFollowingFeed && (
<View style={[pal.border, a.border_b]}>
<NoFollowingFeed />
{noFollowingFeed && (
<View style={[pal.border, a.border_b]}>
<NoFollowingFeed />
</View>
)}
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
<Trans>Saved Feeds</Trans>
</Text>
</View>
{preferences ? (
!unpinnedFeeds.length ? (
<View
style={[
pal.border,
isMobile && s.flex1,
pal.viewLight,
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
<Trans>You don't have any saved feeds.</Trans>
</Text>
</View>
)}
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
<Trans>Saved Feeds</Trans>
</Text>
</View>
{preferences ? (
!unpinnedFeeds.length ? (
<View
style={[
pal.border,
isMobile && s.flex1,
pal.viewLight,
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
<Trans>You don't have any saved feeds.</Trans>
</Text>
</View>
) : (
unpinnedFeeds.map(f => (
<ListItem
key={f.id}
feed={f}
isPinned={false}
currentFeeds={currentFeeds}
setCurrentFeeds={setCurrentFeeds}
preferences={preferences}
/>
))
)
) : (
<ActivityIndicator style={{marginTop: 20}} />
)}
unpinnedFeeds.map(f => (
<ListItem
key={f.id}
feed={f}
isPinned={false}
currentFeeds={currentFeeds}
setCurrentFeeds={setCurrentFeeds}
preferences={preferences}
/>
))
)
) : (
<ActivityIndicator style={{marginTop: 20}} />
)}
<View style={styles.footerText}>
<Text type="sm" style={pal.textLight}>
<Trans>
Feeds are custom algorithms that users build with a little
coding expertise.{' '}
<TextLink
type="sm"
style={pal.link}
href="https://github.com/bluesky-social/feed-generator"
text={_(msg`See this guide`)}
/>{' '}
for more information.
</Trans>
</Text>
</View>
<View style={{height: 100}} />
</ScrollView>
</CenteredView>
<View style={styles.footerText}>
<Text type="sm" style={pal.textLight}>
<Trans>
Feeds are custom algorithms that users build with a little coding
expertise.{' '}
<TextLink
type="sm"
style={pal.link}
href="https://github.com/bluesky-social/feed-generator"
text={_(msg`See this guide`)}
/>{' '}
for more information.
</Trans>
</Text>
</View>
</Layout.Content>
</Layout.Screen>
)
}
@@ -456,7 +447,6 @@ const styles = StyleSheet.create({
},
footerText: {
paddingHorizontal: 26,
paddingTop: 22,
paddingBottom: 100,
paddingVertical: 22,
},
})
+105 -131
View File
@@ -55,7 +55,6 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {Link} from '#/view/com/util/Link'
import {List} from '#/view/com/util/List'
import {Text} from '#/view/com/util/text/Text'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {Explore} from '#/view/screens/Search/Explore'
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils'
@@ -68,63 +67,46 @@ import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import * as Layout from '#/components/Layout'
function Loader() {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<CenteredView
style={[
// @ts-ignore web only -prf
{
padding: 18,
height: isWeb ? '100vh' : undefined,
},
pal.border,
]}
sideBorders={!isMobile}>
<ActivityIndicator />
</CenteredView>
<Layout.Content>
<View style={[a.py_xl]}>
<ActivityIndicator />
</View>
</Layout.Content>
)
}
function EmptyState({message, error}: {message: string; error?: string}) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<CenteredView
sideBorders={!isMobile}
style={[
pal.border,
// @ts-ignore web only -prf
{
padding: 18,
height: isWeb ? '100vh' : undefined,
},
]}>
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
<Text style={[pal.text]}>{message}</Text>
<Layout.Content>
<View style={[a.p_xl]}>
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
<Text style={[pal.text]}>{message}</Text>
{error && (
<>
<View
style={[
{
marginVertical: 12,
height: 1,
width: '100%',
backgroundColor: pal.text.color,
opacity: 0.2,
},
]}
/>
{error && (
<>
<View
style={[
{
marginVertical: 12,
height: 1,
width: '100%',
backgroundColor: pal.text.color,
opacity: 0.2,
},
]}
/>
<Text style={[pal.textLight]}>
<Trans>Error:</Trans> {error}
</Text>
</>
)}
<Text style={[pal.textLight]}>
<Trans>Error:</Trans> {error}
</Text>
</>
)}
</View>
</View>
</CenteredView>
</Layout.Content>
)
}
@@ -224,7 +206,7 @@ let SearchScreenPostResults = ({
if (item.type === 'post') {
return <Post post={item.post} />
} else {
return <Loader />
return null
}
}}
keyExtractor={item => item.key}
@@ -550,19 +532,13 @@ let SearchScreenInner = ({
<Pager
onPageSelected={onPageSelected}
renderTabBar={props => (
<CenteredView
sideBorders
<Layout.Center
style={[
pal.border,
pal.view,
web({
position: isWeb ? 'sticky' : '',
zIndex: 1,
}),
web([a.sticky, a.z_10]),
{top: isWeb ? headerHeight : undefined},
]}>
<TabBar items={sections.map(section => section.title)} {...props} />
</CenteredView>
</Layout.Center>
)}
initialPage={0}>
{sections.map((section, i) => (
@@ -572,7 +548,7 @@ let SearchScreenInner = ({
) : hasSession ? (
<Explore />
) : (
<CenteredView sideBorders style={pal.border}>
<Layout.Center>
<View
// @ts-ignore web only -esb
style={{
@@ -614,7 +590,7 @@ let SearchScreenInner = ({
</Text>
</View>
</View>
</CenteredView>
</Layout.Center>
)
}
SearchScreenInner = React.memo(SearchScreenInner)
@@ -650,7 +626,7 @@ export function SearchScreen(
* Arbitrary sizing, so guess and check, used for sticky header alignment and
* sizing.
*/
const headerHeight = 64 + (showFilters ? 40 : 0)
const headerHeight = 60 + (showFilters ? 40 : 0)
useFocusEffect(
useNonReactiveCallback(() => {
@@ -861,73 +837,79 @@ export function SearchScreen(
return (
<Layout.Screen testID="searchScreen">
<CenteredView
<View
style={[
a.p_md,
a.pb_sm,
a.gap_sm,
t.atoms.bg,
web({
height: headerHeight,
position: 'sticky',
top: 0,
zIndex: 1,
}),
]}
sideBorders={gtMobile}>
<View style={[a.flex_row, a.gap_sm]}>
{!gtMobile && (
<Button
testID="viewHeaderBackOrMenuBtn"
onPress={onPressMenu}
hitSlop={HITSLOP_10}
label={_(msg`Menu`)}
accessibilityHint={_(msg`Access navigation links and settings`)}
size="large"
variant="solid"
color="secondary"
shape="square">
<ButtonIcon icon={Menu} size="lg" />
</Button>
)}
<View style={[a.flex_1]}>
<SearchInput
ref={textInput}
value={searchText}
onFocus={onSearchInputFocus}
onChangeText={onChangeText}
onClearText={onPressClearQuery}
onSubmitEditing={onSubmit}
/>
</View>
{showAutocomplete && (
<Button
label={_(msg`Cancel search`)}
size="large"
variant="ghost"
color="secondary"
style={[a.px_sm]}
onPress={onPressCancelSearch}
hitSlop={HITSLOP_10}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
)}
</View>
{showFilters && (
<View
style={[a.flex_row, a.align_center, a.justify_between, a.gap_sm]}>
<View style={[{width: 140}]}>
<SearchLanguageDropdown
value={params.lang}
onChange={params.setLang}
/>
]}>
<Layout.Center>
<View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}>
<View style={[a.flex_row, a.gap_sm]}>
{!gtMobile && (
<Button
testID="viewHeaderBackOrMenuBtn"
onPress={onPressMenu}
hitSlop={HITSLOP_10}
label={_(msg`Menu`)}
accessibilityHint={_(
msg`Access navigation links and settings`,
)}
size="large"
variant="solid"
color="secondary"
shape="square">
<ButtonIcon icon={Menu} size="lg" />
</Button>
)}
<View style={[a.flex_1]}>
<SearchInput
ref={textInput}
value={searchText}
onFocus={onSearchInputFocus}
onChangeText={onChangeText}
onClearText={onPressClearQuery}
onSubmitEditing={onSubmit}
/>
</View>
{showAutocomplete && (
<Button
label={_(msg`Cancel search`)}
size="large"
variant="ghost"
color="secondary"
style={[a.px_sm]}
onPress={onPressCancelSearch}
hitSlop={HITSLOP_10}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
)}
</View>
{showFilters && (
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_sm,
]}>
<View style={[{width: 140}]}>
<SearchLanguageDropdown
value={params.lang}
onChange={params.setLang}
/>
</View>
</View>
)}
</View>
)}
</CenteredView>
</Layout.Center>
</View>
<View
style={{
@@ -992,10 +974,7 @@ let AutocompleteResults = ({
!moderationOpts ? (
<Loader />
) : (
<ScrollView
style={{height: '100%'}}
// @ts-ignore web only -prf
dataSet={{stableGutters: '1'}}
<Layout.Content
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag">
<SearchLinkCard
@@ -1020,7 +999,7 @@ let AutocompleteResults = ({
/>
))}
<View style={{height: 200}} />
</ScrollView>
</Layout.Content>
)}
</>
)
@@ -1042,17 +1021,12 @@ function SearchHistory({
onRemoveItemClick: (item: string) => void
onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
}) {
const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
const {_} = useLingui()
return (
<CenteredView
sideBorders={isTabletOrDesktop}
// @ts-ignore web only -prf
style={{
height: isWeb ? '100vh' : undefined,
}}>
<Layout.Content>
<View style={styles.searchHistoryContainer}>
{(searchHistory.length > 0 || selectedProfiles.length > 0) && (
<Text style={[pal.text, styles.searchHistoryTitle]}>
@@ -1152,7 +1126,7 @@ function SearchHistory({
</View>
)}
</View>
</CenteredView>
</Layout.Content>
)
}
+7 -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 {RemoveScrollBar} from 'react-remove-scroll-bar'
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,12 @@ export function Composer({}: {winHeight: number}) {
return <View />
}
return <Inner state={state} />
return (
<>
<RemoveScrollBar />
<Inner state={state} />
</>
)
}
function Inner({state}: {state: ComposerOpts}) {
+54 -79
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
@@ -220,35 +176,44 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
]}>
{isCurrent ? iconFilled : icon}
{typeof count === 'string' && count ? (
<Text
accessibilityLabel={_(msg`${count} unread items`)}
accessibilityHint=""
accessible={true}
<View
style={[
a.absolute,
a.text_xs,
a.font_bold,
a.rounded_full,
a.text_center,
{
top: '-10%',
left: count.length === 1 ? '50%' : '40%',
backgroundColor: t.palette.primary_500,
color: t.palette.white,
lineHeight: a.text_sm.fontSize,
paddingHorizontal: 4,
paddingVertical: 1,
minWidth: 16,
},
isTablet && [
{
top: '10%',
left: count.length === 1 ? '50%' : '40%',
},
],
a.inset_0,
{right: -20}, // more breathing room
]}>
{count}
</Text>
<Text
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 ? 12 : 8,
backgroundColor: t.palette.primary_500,
color: t.palette.white,
lineHeight: a.text_sm.fontSize,
paddingHorizontal: 4,
paddingVertical: 1,
minWidth: 16,
},
isTablet && [
{
top: '10%',
left: count.length === 1 ? 20 : 16,
},
],
]}>
{count}
</Text>
</View>
) : null}
</View>
{gtTablet && (
@@ -366,9 +331,9 @@ export function DesktopLeftNav() {
<View
role="navigation"
style={[
a.px_xl,
styles.leftNav,
isTablet && styles.leftNavTablet,
pal.view,
pal.border,
]}>
{hasSession ? (
@@ -381,8 +346,6 @@ export function DesktopLeftNav() {
{hasSession && (
<>
<BackBtn />
<NavItem
href="/"
icon={
@@ -525,8 +488,17 @@ 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%',
},
...a.scrollbar_offset.transform,
],
width: 240,
// @ts-ignore web only
maxHeight: 'calc(100vh - 10px)',
overflowY: 'auto',
@@ -538,7 +510,10 @@ const styles = StyleSheet.create({
borderRightWidth: 1,
height: '100%',
width: 76,
paddingLeft: 0,
paddingRight: 0,
alignItems: 'center',
transform: [],
},
profileCard: {
+8 -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,13 @@ 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,
},
...a.scrollbar_offset.transform,
],
maxHeight: '100%',
overflowY: 'auto',
},
+28 -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 {RemoveScrollBar} from 'react-remove-scroll-bar'
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,34 @@ 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 />
<>
<RemoveScrollBar />
<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>
</>
)}
</>
)