ALF lists screen (#8941)

* alf list screens

* relocate to `#/screens`, balkanize

* use useBreakpoints

* showCancel on subscribe menu

* fix typo
This commit is contained in:
Samuel Newman
2025-09-09 20:20:33 +03:00
committed by GitHub
parent 4a1b1f17f4
commit 6432667f60
10 changed files with 1226 additions and 1079 deletions
+136
View File
@@ -0,0 +1,136 @@
import {useCallback, useImperativeHandle, useState} from 'react'
import {View} from 'react-native'
import {type AppBskyGraphDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isNative} from '#/platform/detection'
import {useSession} from '#/state/session'
import {ListMembers} from '#/view/com/lists/ListMembers'
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, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
interface SectionRef {
scrollToTop: () => void
}
interface AboutSectionProps {
ref?: React.Ref<SectionRef>
list: AppBskyGraphDefs.ListView
onPressAddUser: () => void
headerHeight: number
scrollElRef: ListRef
}
export function AboutSection({
ref,
list,
onPressAddUser,
headerHeight,
scrollElRef,
}: AboutSectionProps) {
const {_} = useLingui()
const {currentAccount} = useSession()
const {gtMobile} = useBreakpoints()
const [isScrolledDown, setIsScrolledDown] = useState(false)
const isOwner = list.creator.did === currentAccount?.did
const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({
animated: isNative,
offset: -headerHeight,
})
}, [scrollElRef, headerHeight])
useImperativeHandle(ref, () => ({
scrollToTop: onScrollToTop,
}))
const renderHeader = useCallback(() => {
if (!isOwner) {
return <View />
}
if (!gtMobile) {
return (
<View style={[a.px_sm, a.py_sm]}>
<Button
testID="addUserBtn"
label={_(msg`Add a user to this list`)}
onPress={onPressAddUser}
color="primary"
size="small"
variant="outline"
style={[a.py_md]}>
<ButtonIcon icon={PersonPlusIcon} />
<ButtonText>
<Trans>Add people</Trans>
</ButtonText>
</Button>
</View>
)
}
return (
<View style={[a.px_lg, a.py_md, a.flex_row_reverse]}>
<Button
testID="addUserBtn"
label={_(msg`Add a user to this list`)}
onPress={onPressAddUser}
color="primary"
size="small"
variant="ghost"
style={[a.py_sm]}>
<ButtonIcon icon={PersonPlusIcon} />
<ButtonText>
<Trans>Add people</Trans>
</ButtonText>
</Button>
</View>
)
}, [isOwner, _, onPressAddUser, gtMobile])
const renderEmptyState = useCallback(() => {
return (
<View style={[a.gap_xl, a.align_center]}>
<EmptyState icon="users-slash" message={_(msg`This list is empty.`)} />
{isOwner && (
<Button
testID="emptyStateAddUserBtn"
label={_(msg`Start adding people`)}
onPress={onPressAddUser}
color="primary"
size="small">
<ButtonIcon icon={PersonPlusIcon} />
<ButtonText>
<Trans>Start adding people!</Trans>
</ButtonText>
</Button>
)}
</View>
)
}, [_, onPressAddUser, isOwner])
return (
<View>
<ListMembers
testID="listItems"
list={list.uri}
scrollElRef={scrollElRef}
renderHeader={renderHeader}
renderEmptyState={renderEmptyState}
headerOffset={headerHeight}
onScrolledDownChange={setIsScrolledDown}
/>
{isScrolledDown && (
<LoadLatestBtn
onPress={onScrollToTop}
label={_(msg`Scroll to top`)}
showIndicator={false}
/>
)}
</View>
)
}