diff --git a/src/components/ProfileBadges.tsx b/src/components/ProfileBadges.tsx index 1c376ca57..b9f63084e 100644 --- a/src/components/ProfileBadges.tsx +++ b/src/components/ProfileBadges.tsx @@ -63,6 +63,8 @@ type ProfileBadgesProps = ViewStyleProp & { profile: bsky.profile.AnyProfileView interactive?: boolean pdsInteractive?: boolean + /** The beta badge is reserved for the full profile header. */ + showBetaBadge?: boolean size: Size allowFontScaling?: boolean } @@ -80,6 +82,7 @@ export function ProfileBadgesFromProfileShadow({ profile, interactive = false, pdsInteractive = true, + showBetaBadge = false, size, style, allowFontScaling = true, @@ -110,7 +113,7 @@ export function ProfileBadgesFromProfileShadow({ shouldResolvePds && (isPdsLoading || (!!pdsData && !(hideBskyPds && pdsData.isBsky))) - const isBetaBadgeVisible = useIsBetaBadgeVisible(shadowed) + const isBetaBadgeVisible = useIsBetaBadgeVisible(shadowed) && showBetaBadge const badgeVisibility = [ verification.showBadge, isBetaBadgeVisible, @@ -173,12 +176,14 @@ export function ProfileBadgesFromProfileShadow({ width={verificationIconWidth} hitSlop={hitSlops[0]} /> - + {showBetaBadge ? ( + + ) : null} ) : null} - + {showBetaBadge ? ( + + ) : null} diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index 513be7395..130a26363 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -54,7 +54,12 @@ export function ProfileHeaderDisplayName({ primaryName )} - + {/* * TODO: Workaround for a rounding bug in Android RN. diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index bf1c8b6b2..55390eb66 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -272,23 +272,45 @@ let NotificationFeedItem = ({ ) } - const firstAuthorLink = ( - - - {forceLTR(firstAuthorName)} + /* + * A View nested in native Text is laid out as an attachment and inflates the + * line box. Keep badges inline on web, but make them a sibling on native. + */ + const firstAuthorNameLink = ( + + {forceLTR(firstAuthorName)} + {IS_WEB ? ( - + ) : null} + + ) + const firstAuthorLink = IS_WEB ? ( + + {firstAuthorNameLink} + ) : ( + + + {firstAuthorNameLink} + + + ) const additionalAuthorsCount = authors.length - 1 const hasMultipleAuthors = additionalAuthorsCount > 0 @@ -380,7 +402,7 @@ let NotificationFeedItem = ({ /* * Follow-backs are ungrouped, grouped follow-backs not supported atm, * see `src/state/queries/notifications/util.ts` - */ + */ a11yLabel = starterPackName ? _(msg`${firstAuthorName} followed you back via starter pack ${starterPackName}`) : _(msg`${firstAuthorName} followed you back`) @@ -478,7 +500,7 @@ let NotificationFeedItem = ({ ) icon = ( - + ) } else if (item.type === 'verified') { @@ -824,8 +846,14 @@ function FollowedViaStarterPack({ via starter pack{' '} + ) + + if (!IS_WEB) { + groupConjunctionWithAuthorCount(inlineChildren) + + const lastChild = inlineChildren.at(-1) + if ( + isValidElement<{children?: React.ReactNode}>(lastChild) && + lastChild.type === Text + ) { + const lastContent = + typeof lastChild.props.children === 'string' + ? lastChild.props.children.trimStart() + : lastChild.props.children + inlineChildren[inlineChildren.length - 1] = cloneElement( + lastChild, + undefined, + lastContent, + timestampElement, + ) + } else { + inlineChildren.push(timestampElement) + } + } else { + inlineChildren.push(timestampElement) + } + return ( - {renderInlineTransChildren(children, t)} - + {inlineChildren} ) } +function groupConjunctionWithAuthorCount(children: React.ReactNode[]): void { + const authorCountIndex = children.findIndex(isSemiboldTextElement) + if (authorCountIndex < 1) return + + const conjunction = children[authorCountIndex - 1] + const authorCount = children[authorCountIndex] + if ( + !isValidElement<{children?: React.ReactNode}>(conjunction) || + conjunction.type !== Text + ) { + return + } + + children.splice( + authorCountIndex - 1, + 2, + cloneElement( + conjunction, + undefined, + typeof conjunction.props.children === 'string' + ? conjunction.props.children.trimStart() + : conjunction.props.children, + authorCount, + ' ', + ), + ) +} + +function isSemiboldTextElement(child: React.ReactNode): boolean { + if ( + !isValidElement<{ + style?: React.ComponentProps['style'] + }>(child) || + child.type !== Text + ) { + return false + } + const style = StyleSheet.flatten(child.props.style) + const semiboldStyle = StyleSheet.flatten(a.font_semi_bold) + return style?.fontWeight === semiboldStyle.fontWeight +} + function renderInlineTransChildren( children: React.ReactNode, t: ReturnType, ): React.ReactNode { return Children.map(children, (child, i) => { if (typeof child === 'string') { - if (!child) { + if (!child || (!IS_WEB && child.trim().length === 0)) { return null } return (