From 3e8171ca343be911d8f68c922a9b4fc0f7c9c9e1 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Fri, 28 Jun 2024 17:21:15 -0400 Subject: [PATCH] list item folder refactor --- .../ui/src/components/ChannelNavSection.tsx | 2 +- .../ui/src/components/ChannelNavSections.tsx | 2 +- packages/ui/src/components/ChatList.tsx | 26 ++- packages/ui/src/components/ContactList.tsx | 55 +----- .../GroupListItem/GroupListItem.tsx | 29 ---- .../GroupListItem/GroupListItem.web.tsx | 32 ---- .../ui/src/components/GroupListItem/index.ts | 1 - .../ChannelListItem.tsx} | 4 +- .../src/components/ListItem/ChatListItem.tsx | 69 ++++++++ .../components/ListItem/ContactListItem.tsx | 54 ++++++ .../src/components/ListItem/GroupListItem.tsx | 164 ++++++++++++++++++ .../components/{ => ListItem}/ListItem.tsx | 14 +- .../{ => ListItem}/SwipableChatListItem.tsx | 6 +- packages/ui/src/components/ListItem/index.ts | 6 + packages/ui/src/index.ts | 3 +- 15 files changed, 325 insertions(+), 142 deletions(-) delete mode 100644 packages/ui/src/components/GroupListItem/GroupListItem.tsx delete mode 100644 packages/ui/src/components/GroupListItem/GroupListItem.web.tsx delete mode 100644 packages/ui/src/components/GroupListItem/index.ts rename packages/ui/src/components/{ChannelListItem/index.tsx => ListItem/ChannelListItem.tsx} (98%) create mode 100644 packages/ui/src/components/ListItem/ChatListItem.tsx create mode 100644 packages/ui/src/components/ListItem/ContactListItem.tsx create mode 100644 packages/ui/src/components/ListItem/GroupListItem.tsx rename packages/ui/src/components/{ => ListItem}/ListItem.tsx (97%) rename packages/ui/src/components/{ => ListItem}/SwipableChatListItem.tsx (98%) create mode 100644 packages/ui/src/components/ListItem/index.ts diff --git a/packages/ui/src/components/ChannelNavSection.tsx b/packages/ui/src/components/ChannelNavSection.tsx index 22de39b697..ab29a50190 100644 --- a/packages/ui/src/components/ChannelNavSection.tsx +++ b/packages/ui/src/components/ChannelNavSection.tsx @@ -2,7 +2,7 @@ import * as db from '@tloncorp/shared/dist/db'; import { useCallback, useMemo } from 'react'; import { SizableText, YStack } from '../core'; -import ChannelListItem from './ChannelListItem'; +import { ChannelListItem } from './ListItem'; export default function ChannelNavSection({ section, diff --git a/packages/ui/src/components/ChannelNavSections.tsx b/packages/ui/src/components/ChannelNavSections.tsx index eb8715c081..05aaa2fd6c 100644 --- a/packages/ui/src/components/ChannelNavSections.tsx +++ b/packages/ui/src/components/ChannelNavSections.tsx @@ -2,8 +2,8 @@ import * as db from '@tloncorp/shared/dist/db'; import { useMemo } from 'react'; import { SizableText, YStack } from '../core'; -import ChannelListItem from './ChannelListItem'; import ChannelNavSection from './ChannelNavSection'; +import { ChannelListItem } from './ListItem'; export default function ChannelNavSections({ group, diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 98badb15f5..badf830970 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -14,13 +14,11 @@ import { } from 'react-native'; import { useStyle } from '../core'; -import ChannelListItem from './ChannelListItem'; -import { GroupListItem } from './GroupListItem'; -import { ListItemProps } from './ListItem'; +import { ChannelListItem, GroupListItem, ListItemProps } from './ListItem'; +import { SwipableChatRow } from './ListItem/SwipableChatListItem'; import { SectionListHeader } from './SectionList'; -import { SwipableChatRow } from './SwipableChatListItem'; -type ListItem = db.Channel | db.Group; +export type Chat = db.Channel | db.Group; export function ChatList({ pinned, @@ -30,8 +28,8 @@ export function ChatList({ onPressItem, onSectionChange, }: store.CurrentChats & { - onPressItem?: (chat: ListItem) => void; - onLongPressItem?: (chat: ListItem) => void; + onPressItem?: (chat: Chat) => void; + onLongPressItem?: (chat: Chat) => void; onSectionChange?: (title: string) => void; }) { const data = useMemo(() => { @@ -54,7 +52,7 @@ export function ChatList({ ) as StyleProp; const renderItem = useCallback( - ({ item }: SectionListRenderItemInfo) => { + ({ item }: SectionListRenderItemInfo) => { return ( ; - }) => { + ({ section }: { section: SectionListData }) => { return ( {section.title} @@ -116,7 +110,7 @@ export function ChatList({ } ).current; - const getChannelKey = useCallback((item: ListItem) => { + const getChannelKey = useCallback((item: Chat) => { if (!item || typeof item !== 'object' || !item.id) { return 'invalid-item'; } @@ -146,7 +140,7 @@ export function ChatList({ } const ChatListItem = React.memo(function ChatListItemComponent( - props: ListItemProps + props: ListItemProps ) { return logic.isChannel(props.model) ? ( @@ -162,7 +156,7 @@ const ChatListItemContent = React.memo(function ChatListItemContentComponent({ onPress, onLongPress, ...props -}: ListItemProps) { +}: ListItemProps) { const handlePress = useCallback(() => { onPress?.(model); }, [model, onPress]); diff --git a/packages/ui/src/components/ContactList.tsx b/packages/ui/src/components/ContactList.tsx index 71ca3c74c9..b1a2e6164a 100644 --- a/packages/ui/src/components/ContactList.tsx +++ b/packages/ui/src/components/ContactList.tsx @@ -1,59 +1,8 @@ -import * as db from '@tloncorp/shared/dist/db'; -import { ComponentProps, PropsWithChildren } from 'react'; +import { PropsWithChildren } from 'react'; import { styled, withStaticProperties } from 'tamagui'; import { YStack } from '../core'; -import ContactName from './ContactName'; -import { ListItem } from './ListItem'; - -const ContactListItem = ({ - contact, - onPress, - onLongPress, - showNickname = false, - showUserId = false, - full = false, - showIcon = true, - matchText, - ...props -}: { - contact: db.Contact; - onPress?: (contact: db.Contact) => void; - onLongPress?: () => void; - showNickname?: boolean; - showUserId?: boolean; - full?: boolean; - showIcon?: boolean; - matchText?: string; -} & ComponentProps) => ( - onPress?.(contact)} - onLongPress={onLongPress} - alignItems="center" - justifyContent="flex-start" - padding="$s" - {...props} - > - {showIcon && ( - - )} - - - - -); +import { ContactListItem } from './ListItem/ContactListItem'; const ContactListFrame = styled(YStack, { gap: '$s', diff --git a/packages/ui/src/components/GroupListItem/GroupListItem.tsx b/packages/ui/src/components/GroupListItem/GroupListItem.tsx deleted file mode 100644 index a42fd893c8..0000000000 --- a/packages/ui/src/components/GroupListItem/GroupListItem.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type * as db from '@tloncorp/shared/dist/db'; -import { useCallback } from 'react'; - -import type { ListItemProps } from '../ListItem'; -import GroupListItemContent from './GroupListItemContent'; - -export const GroupListItem = ({ - model, - onPress, - onLongPress, - ...props -}: ListItemProps) => { - const handlePress = useCallback(() => { - onPress?.(model); - }, [onPress, model]); - - const handleLongPress = useCallback(() => { - onLongPress?.(model); - }, [onLongPress, model]); - - return ( - - ); -}; diff --git a/packages/ui/src/components/GroupListItem/GroupListItem.web.tsx b/packages/ui/src/components/GroupListItem/GroupListItem.web.tsx deleted file mode 100644 index 6c334b400b..0000000000 --- a/packages/ui/src/components/GroupListItem/GroupListItem.web.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type * as db from '@tloncorp/shared/dist/db'; -import { useLongPress } from '@uidotdev/usehooks'; - -import type { ListItemProps } from '../ListItem'; -import GroupListItemContent from './GroupListItemContent'; - -export const GroupListItem = ({ - model, - onPress, - onLongPress, - unreadCount, - ...props -}: ListItemProps) => { - // TODO: Figure out if this is necessary. Why can't we use Tamagui's long press handler? - const attributes = useLongPress( - () => { - onLongPress?.(model); - }, - { threshold: 600 } - ); - - return ( -
- -
- ); -}; diff --git a/packages/ui/src/components/GroupListItem/index.ts b/packages/ui/src/components/GroupListItem/index.ts deleted file mode 100644 index bb14079859..0000000000 --- a/packages/ui/src/components/GroupListItem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './GroupListItem'; diff --git a/packages/ui/src/components/ChannelListItem/index.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx similarity index 98% rename from packages/ui/src/components/ChannelListItem/index.tsx rename to packages/ui/src/components/ListItem/ChannelListItem.tsx index b313777277..6717badced 100644 --- a/packages/ui/src/components/ChannelListItem/index.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -10,9 +10,9 @@ import { ListItem, ListItemIconContainer, type ListItemProps, -} from '../ListItem'; +} from './ListItem'; -export default function ChannelListItem({ +export function ChannelListItem({ model, useTypeIcon, onPress, diff --git a/packages/ui/src/components/ListItem/ChatListItem.tsx b/packages/ui/src/components/ListItem/ChatListItem.tsx new file mode 100644 index 0000000000..06892081d7 --- /dev/null +++ b/packages/ui/src/components/ListItem/ChatListItem.tsx @@ -0,0 +1,69 @@ +import * as logic from '@tloncorp/shared/dist/logic'; +import React, { useCallback } from 'react'; + +import { Chat } from '../ChatList'; +import { ChannelListItem } from './ChannelListItem'; +import { GroupListItem } from './GroupListItem'; +import { ListItemProps } from './ListItem'; + +export const ChatListItem = React.memo(function ChatListItemComponent({ + model, + onPress, + onLongPress, + ...props +}: ListItemProps) { + const handlePress = useCallback(() => { + onPress?.(model); + }, [model, onPress]); + + const handleLongPress = useCallback(() => { + onLongPress?.(model); + }, [model, onLongPress]); + + // if the chat list item is a group, it's pending + if (logic.isGroup(model)) { + return ( + + ); + } + + if (logic.isChannel(model)) { + if ( + model.type === 'dm' || + model.type === 'groupDm' || + model.pin?.type === 'channel' + ) { + return ( + + ); + } else if (model.group) { + return ( + + ); + } + } + + console.warn('unable to render chat list item', model.id, model); + return null; +}); diff --git a/packages/ui/src/components/ListItem/ContactListItem.tsx b/packages/ui/src/components/ListItem/ContactListItem.tsx new file mode 100644 index 0000000000..111229b883 --- /dev/null +++ b/packages/ui/src/components/ListItem/ContactListItem.tsx @@ -0,0 +1,54 @@ +import * as db from '@tloncorp/shared/dist/db'; +import { ComponentProps } from 'react'; + +import ContactName from '../ContactName'; +import { ListItem } from './ListItem'; + +export const ContactListItem = ({ + contact, + onPress, + onLongPress, + showNickname = false, + showUserId = false, + full = false, + showIcon = true, + matchText, + ...props +}: { + contact: db.Contact; + onPress?: (contact: db.Contact) => void; + onLongPress?: () => void; + showNickname?: boolean; + showUserId?: boolean; + full?: boolean; + showIcon?: boolean; + matchText?: string; +} & ComponentProps) => ( + onPress?.(contact)} + onLongPress={onLongPress} + alignItems="center" + justifyContent="flex-start" + padding="$s" + {...props} + > + {showIcon && ( + + )} + + + + +); diff --git a/packages/ui/src/components/ListItem/GroupListItem.tsx b/packages/ui/src/components/ListItem/GroupListItem.tsx new file mode 100644 index 0000000000..92c30e180f --- /dev/null +++ b/packages/ui/src/components/ListItem/GroupListItem.tsx @@ -0,0 +1,164 @@ +import type * as db from '@tloncorp/shared/dist/db'; +import { useCallback, useMemo } from 'react'; + +import { useCalm } from '../../contexts/calm'; +import { View, XStack } from '../../core'; +import * as utils from '../../utils'; +import { getBackgroundColor } from '../../utils/colorUtils'; +import { Badge } from '../Badge'; +import ContactName from '../ContactName'; +import { Icon } from '../Icon'; +import type { ListItemProps } from './ListItem'; +import { ListItem } from './ListItem'; + +export const GroupListItem = ({ + model, + onPress, + onLongPress, + ...props +}: ListItemProps) => { + const handlePress = useCallback(() => { + onPress?.(model); + }, [onPress, model]); + + const handleLongPress = useCallback(() => { + onLongPress?.(model); + }, [onLongPress, model]); + + return ( + + ); +}; + +export default function GroupListItemContent({ + model, + onPress, + onLongPress, + ...props +}: ListItemProps) { + const countToShow = model.unread?.count ?? 0; + const { disableAvatars } = useCalm(); + // Fallback color for calm mode or unset colors + const colors = { backgroundColor: '$secondaryBackground' }; + + const { isPending, statusDisplay, isErrored } = useMemo( + () => getDisplayInfo(model), + [model] + ); + + return ( + onPress?.(model)} + onLongPress={() => onLongPress?.(model)} + > + + + + + + {model.title ?? model.id} + + {model.lastPost && ( + + + + {model.lastChannel} + + + )} + {!isPending && model.lastPost ? ( + + + : {model.lastPost?.textContent ?? ''} + + ) : null} + + {statusDisplay ? ( + + + + ) : ( + + + 0 || model.volumeSettings?.isMuted ? 1 : 0} + muted={model.volumeSettings?.isMuted ?? false} + > + {utils.displayableUnreadCount(countToShow)} + + + )} + + ); +} + +function getLastMessageIcon(type: db.Post['type']) { + switch (type) { + case 'chat': + return 'ChannelTalk'; + case 'block': + return 'ChannelGalleries'; + case 'note': + return 'ChannelNotebooks'; + default: + return 'Channel'; + } +} + +type DisplayInfo = { + statusDisplay: string; + isPending: boolean; + isErrored: boolean; + isNew: boolean; +}; + +function getDisplayInfo(group: db.Group): DisplayInfo { + return { + isPending: group.currentUserIsMember === false, + isErrored: group.joinStatus === 'errored', + isNew: group.currentUserIsMember && !!group.isNew, + statusDisplay: + group.currentUserIsMember && group.isNew + ? 'NEW' + : group.haveRequestedInvite + ? 'Requested' + : group.haveInvite + ? 'Invite' + : group.joinStatus === 'errored' + ? 'Errored' + : group.joinStatus === 'joining' + ? 'Joining' + : '', + }; +} diff --git a/packages/ui/src/components/ListItem.tsx b/packages/ui/src/components/ListItem/ListItem.tsx similarity index 97% rename from packages/ui/src/components/ListItem.tsx rename to packages/ui/src/components/ListItem/ListItem.tsx index fa9c065d28..2b330e6e2b 100644 --- a/packages/ui/src/components/ListItem.tsx +++ b/packages/ui/src/components/ListItem/ListItem.tsx @@ -8,9 +8,17 @@ import { } from 'react'; import { ColorProp, SizeTokens, styled, withStaticProperties } from 'tamagui'; -import { Image, SizableText, Stack, Text, View, XStack, YStack } from '../core'; -import { Avatar, AvatarSize } from './Avatar'; -import { Icon, IconType } from './Icon'; +import { + Image, + SizableText, + Stack, + Text, + View, + XStack, + YStack, +} from '../../core'; +import { Avatar, AvatarSize } from '../Avatar'; +import { Icon, IconType } from '../Icon'; export interface BaseListItemProps { model: T; diff --git a/packages/ui/src/components/SwipableChatListItem.tsx b/packages/ui/src/components/ListItem/SwipableChatListItem.tsx similarity index 98% rename from packages/ui/src/components/SwipableChatListItem.tsx rename to packages/ui/src/components/ListItem/SwipableChatListItem.tsx index 3d424c5a1a..453ee79802 100644 --- a/packages/ui/src/components/SwipableChatListItem.tsx +++ b/packages/ui/src/components/ListItem/SwipableChatListItem.tsx @@ -14,9 +14,9 @@ import { Animated, TouchableOpacity } from 'react-native'; import Swipeable from 'react-native-gesture-handler/Swipeable'; import { ColorTokens, Stack } from 'tamagui'; -import { XStack } from '../core'; -import * as utils from '../utils'; -import { Icon, IconType } from './Icon'; +import { XStack } from '../../core'; +import * as utils from '../../utils'; +import { Icon, IconType } from '../Icon'; function BaseSwipableChatRow({ model, diff --git a/packages/ui/src/components/ListItem/index.ts b/packages/ui/src/components/ListItem/index.ts new file mode 100644 index 0000000000..50a89dcb83 --- /dev/null +++ b/packages/ui/src/components/ListItem/index.ts @@ -0,0 +1,6 @@ +export * from './GroupListItem'; +export * from './ChatListItem'; +export * from './ContactListItem'; +export * from './ChannelListItem'; +export * from './SwipableChatListItem'; +export * from './ListItem'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 433ff6f28b..332b47afc0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -19,10 +19,11 @@ export * from './components/View'; export * from './components/Sheet'; export * from './components/GroupPreviewSheet'; export * from './components/NavBar'; +export * from './components/ListItem'; export * from './components/GroupOptionsSheet'; export * from './components/WelcomeSheet'; export * from './components/AddChats/AddDmSheet'; -export * from './components/GroupListItem'; +export * from './components/ListItem'; export * from './components/FloatingActionButton'; export * from './components/ProfileSheet'; export * from './components/ChatList';