[APP-1403] Profile empty states (#8969)
* update: Empty state component * update type error fixes * add empty state icon * update translations on labels * update: lint error for empty state * remove unused prompt * update type errors * fix lint errors and update profile followers and profile follows * updated starterpack * fix lint errors * address feedback * update icons to be a react element * update icons * update viewbox for icons * optimize: icons and update profile lists * optimize: icons and update profile lists * lint error fix * update iconSize from the rebase
This commit is contained in:
committed by
GitHub
parent
2d056e5be6
commit
d0649e9a9e
@@ -7,7 +7,11 @@ import {
|
||||
} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {
|
||||
type NavigationProp,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
} from '@react-navigation/native'
|
||||
|
||||
import {useCleanError} from '#/lib/hooks/useCleanError'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
@@ -21,12 +25,12 @@ import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation
|
||||
import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Post} from '#/view/com/post/Post'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {PostFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {EmptyState} from '#/screens/Bookmarks/components/EmptyState'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {BookmarkFilled} from '#/components/icons/Bookmark'
|
||||
import {BookmarkDeleteLarge, BookmarkFilled} from '#/components/icons/Bookmark'
|
||||
import {CircleQuestion_Stroke2_Corner2_Rounded as QuestionIcon} from '#/components/icons/CircleQuestion'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
@@ -259,13 +263,35 @@ function BookmarkNotFound({
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarksEmpty() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const navigation = useNavigation<NavigationProp<CommonNavigatorParams>>()
|
||||
|
||||
return (
|
||||
<EmptyState
|
||||
icon={BookmarkDeleteLarge}
|
||||
message={_(msg`Nothing saved yet`)}
|
||||
textStyle={[t.atoms.text_contrast_medium, a.font_medium]}
|
||||
button={{
|
||||
label: _(msg`Button to go back to the home timeline`),
|
||||
text: _(msg`Go home`),
|
||||
onPress: () => navigation.navigate('Home' as never),
|
||||
size: 'small',
|
||||
color: 'secondary',
|
||||
}}
|
||||
style={[a.pt_3xl]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function renderItem({item, index}: {item: ListItem; index: number}) {
|
||||
switch (item.type) {
|
||||
case 'loading': {
|
||||
return <PostFeedLoadingPlaceholder />
|
||||
}
|
||||
case 'empty': {
|
||||
return <EmptyState />
|
||||
return <BookmarksEmpty />
|
||||
}
|
||||
case 'bookmark': {
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,7 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {type AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
|
||||
@@ -35,7 +36,11 @@ export function NotificationsActivityListScreen({
|
||||
feed={`posts|${uris}`}
|
||||
disablePoll
|
||||
renderEmptyState={() => (
|
||||
<EmptyState icon="growth" message={_(msg`No posts here`)} />
|
||||
<EmptyState
|
||||
icon={EditIcon}
|
||||
iconSize="2xl"
|
||||
message={_(msg`No posts here`)}
|
||||
/>
|
||||
)}
|
||||
renderEndOfFeed={() => <ListFooter />}
|
||||
/>
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
ProfileFeedHeader,
|
||||
ProfileFeedHeaderSkeleton,
|
||||
} from '#/screens/Profile/components/ProfileFeedHeader'
|
||||
import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
|
||||
@@ -189,7 +190,13 @@ export function ProfileFeedScreenInner({
|
||||
}, [onScrollToTop, isScreenFocused])
|
||||
|
||||
const renderPostsEmpty = useCallback(() => {
|
||||
return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
|
||||
return (
|
||||
<EmptyState
|
||||
icon={HashtagWideIcon}
|
||||
iconSize="2xl"
|
||||
message={_(msg`This feed is empty.`)}
|
||||
/>
|
||||
)
|
||||
}, [_])
|
||||
|
||||
const isVideoFeed = React.useMemo(() => {
|
||||
|
||||
@@ -6,14 +6,20 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {isIOS, isNative} from '#/platform/detection'
|
||||
import {type FeedDescriptor} from '#/state/queries/post-feed'
|
||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||
import {
|
||||
type FeedDescriptor,
|
||||
RQKEY as FEED_RQKEY,
|
||||
} from '#/state/queries/post-feed'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {
|
||||
EmptyState,
|
||||
type EmptyStateButtonProps,
|
||||
} from '#/view/com/util/EmptyState'
|
||||
import {type ListRef} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {atoms as a, ios, useTheme} from '#/alf'
|
||||
import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {type SectionRef} from './types'
|
||||
|
||||
@@ -25,7 +31,11 @@ interface FeedSectionProps {
|
||||
scrollElRef: ListRef
|
||||
ignoreFilterFor?: string
|
||||
setScrollViewTag: (tag: number | null) => void
|
||||
emptyStateMessage?: string
|
||||
emptyStateButton?: EmptyStateButtonProps
|
||||
emptyStateIcon?: React.ComponentType<any> | React.ReactElement
|
||||
}
|
||||
|
||||
export function ProfileFeedSection({
|
||||
ref,
|
||||
feed,
|
||||
@@ -34,6 +44,9 @@ export function ProfileFeedSection({
|
||||
scrollElRef,
|
||||
ignoreFilterFor,
|
||||
setScrollViewTag,
|
||||
emptyStateMessage,
|
||||
emptyStateButton,
|
||||
emptyStateIcon,
|
||||
}: FeedSectionProps) {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -44,7 +57,6 @@ export function ProfileFeedSection({
|
||||
const adjustedInitialNumToRender = useInitialNumToRender({
|
||||
screenHeightOffset: headerHeight,
|
||||
})
|
||||
|
||||
const onScrollToTop = useCallback(() => {
|
||||
scrollElRef.current?.scrollToOffset({
|
||||
animated: isNative,
|
||||
@@ -59,8 +71,18 @@ export function ProfileFeedSection({
|
||||
}))
|
||||
|
||||
const renderPostsEmpty = useCallback(() => {
|
||||
return <EmptyState icon="growth" message={_(msg`No posts yet.`)} />
|
||||
}, [_])
|
||||
return (
|
||||
<View style={[a.flex_1, a.justify_center, a.align_center]}>
|
||||
<EmptyState
|
||||
style={{width: '100%'}}
|
||||
icon={emptyStateIcon || EditIcon}
|
||||
iconSize="3xl"
|
||||
message={emptyStateMessage || _(msg`No posts yet`)}
|
||||
button={emptyStateButton}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}, [_, emptyStateButton, emptyStateIcon, emptyStateMessage])
|
||||
|
||||
useEffect(() => {
|
||||
if (isIOS && isFocused && scrollElRef.current) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {type ListRef} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList'
|
||||
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
|
||||
interface SectionRef {
|
||||
@@ -95,7 +96,7 @@ export function AboutSection({
|
||||
const renderEmptyState = useCallback(() => {
|
||||
return (
|
||||
<View style={[a.gap_xl, a.align_center]}>
|
||||
<EmptyState icon="users-slash" message={_(msg`This list is empty.`)} />
|
||||
<EmptyState icon={ListIcon} message={_(msg`This list is empty.`)} />
|
||||
{isOwner && (
|
||||
<Button
|
||||
testID="emptyStateAddUserBtn"
|
||||
@@ -111,7 +112,7 @@ export function AboutSection({
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}, [_, onPressAddUser, isOwner])
|
||||
}, [_, isOwner, onPressAddUser])
|
||||
|
||||
return (
|
||||
<View>
|
||||
|
||||
@@ -7,14 +7,17 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {type FeedDescriptor} from '#/state/queries/post-feed'
|
||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||
import {
|
||||
type FeedDescriptor,
|
||||
RQKEY as FEED_RQKEY,
|
||||
} from '#/state/queries/post-feed'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {type ListRef} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
|
||||
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
|
||||
interface SectionRef {
|
||||
@@ -68,7 +71,11 @@ export function FeedSection({
|
||||
const renderPostsEmpty = useCallback(() => {
|
||||
return (
|
||||
<View style={[a.gap_xl, a.align_center]}>
|
||||
<EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
|
||||
<EmptyState
|
||||
icon={HashtagWideIcon}
|
||||
iconSize="2xl"
|
||||
message={_(msg`This feed is empty.`)}
|
||||
/>
|
||||
{isOwner && (
|
||||
<Button
|
||||
label={_(msg`Start adding people`)}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {atoms as a, useTheme} from '#/alf'
|
||||
import {Admonition, colors} from '#/components/Admonition'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
@@ -102,7 +103,7 @@ export function AppPasswordsScreen({}: Props) {
|
||||
</View>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon="growth"
|
||||
icon={Growth}
|
||||
message={_(msg`No app passwords yet`)}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -201,6 +201,7 @@ export function ItemIcon({
|
||||
lg: 24,
|
||||
xl: 28,
|
||||
'2xl': 32,
|
||||
'3xl': 40,
|
||||
}[size]
|
||||
|
||||
const color =
|
||||
|
||||
Reference in New Issue
Block a user