[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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
))}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user