diff --git a/js/components/src/components/chat/chat-message.tsx b/js/components/src/components/chat/chat-message.tsx index 30007b390..c8338ef11 100644 --- a/js/components/src/components/chat/chat-message.tsx +++ b/js/components/src/components/chat/chat-message.tsx @@ -4,17 +4,29 @@ import { Mention, } from "@atproto/api/dist/client/types/app/bsky/richtext/facet"; import { memo, useCallback } from "react"; -import { Linking, Platform, View } from "react-native"; +import { Linking, Platform, Pressable, View } from "react-native"; import { ChatMessageViewHydrated } from "streamplace"; import { Facet, RichtextSegment, segmentize } from "../../lib/facet"; import { borders, flex, gap, ml, mr, opacity, pl } from "../../lib/theme/atoms"; import { formatHandleWithAt } from "../../utils/format-handle"; -import { atoms, colors, layout } from "../ui"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + atoms, + colors, + layout, + useTheme, +} from "../ui"; import { useLivestreamStore } from "../../livestream-store"; import { Text } from "../ui/text"; import { BadgeDisplayRow } from "./badge"; -import { UserProfileCard } from "./user-profile-card"; +import { + ProfileCardContent, + UserProfileCard, + useProfileCardData, +} from "./user-profile-card"; const getRgbColor = (color?: { red: number; green: number; blue: number }) => color ? `rgb(${color.red}, ${color.green}, ${color.blue})` : colors.gray[500]; @@ -85,6 +97,93 @@ export const RichTextMessage = ({ return segs.map((seg, i) => renderSegment(seg, i, userCache)); }; + +// Web flows the whole message inline inside a single , with the badges + +// handle rendered as an inline-block via display: "inline". +const MessageBodyWeb = ({ item }: { item: ChatMessageViewHydrated }) => { + return ( + + + + + + {formatHandleWithAt(item.author)} + + + + {": "} + + + ); +}; + +// Native can't reliably nest views or images inside , so the badges sit in +// a flex row beside the message instead of inline. Tapping the badges or the +// handle opens the same profile bottom sheet via two triggers on one menu. +const MessageBodyNative = ({ item }: { item: ChatMessageViewHydrated }) => { + const { theme } = useTheme(); + const data = useProfileCardData(item.author, item.badges); + return ( + + {!!item.badges?.length && ( + + + + + + )} + + + + {formatHandleWithAt(item.author)} + + + {": "} + + + + + + + ); +}; + export const RenderChatMessage = memo( function RenderChatMessage({ item, @@ -159,41 +258,11 @@ export const RenderChatMessage = memo( {formatTime(item.record.createdAt)} )} - - - - - - {formatHandleWithAt(item.author)} - - - - {": "} - - + {Platform.OS === "web" ? ( + + ) : ( + + )} ); diff --git a/js/components/src/components/chat/user-profile-card.tsx b/js/components/src/components/chat/user-profile-card.tsx index deb951116..4125f31cb 100644 --- a/js/components/src/components/chat/user-profile-card.tsx +++ b/js/components/src/components/chat/user-profile-card.tsx @@ -92,7 +92,7 @@ interface ProfileCardData { streamer: ProfileViewBasic | undefined; } -function useProfileCardData( +export function useProfileCardData( author: ProfileViewBasic, badges: ChatMessageViewHydrated["badges"], ): ProfileCardData { @@ -181,7 +181,7 @@ const BadgeRow = ({ ); }; -const ProfileCardContent = ({ +export const ProfileCardContent = ({ data, theme, }: {