[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
+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