Improve byline linking and interaction states

This commit is contained in:
Eric Bailey
2026-05-22 11:59:12 -05:00
parent 4fc548eb17
commit 726c5b9a2d
3 changed files with 140 additions and 89 deletions
+10 -2
View File
@@ -326,6 +326,8 @@ export type InlineLinkProps = React.PropsWithChildren<
disableUnderline?: boolean
title?: TextProps['title']
overridePresentation?: boolean
onMouseEnter?: () => void
onMouseLeave?: () => void
}
>
@@ -388,8 +390,14 @@ export function InlineLinkText({
role="link"
onPress={download ? undefined : onPress}
onLongPress={onLongPress}
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
onMouseEnter={() => {
rest.onMouseEnter?.()
onHoverIn()
}}
onMouseLeave={() => {
rest.onMouseLeave?.()
onHoverOut()
}}
accessibilityRole="link"
href={href}
{...web({
@@ -13,9 +13,13 @@ import {Text} from '#/components/Typography'
export function PublicationMetaRow({
view,
author,
onInteractWithin,
onInteractWithout,
}: {
view: AppBskyEmbedExternal.ViewExternal
author: {did: string | null | undefined}
onInteractWithin: () => void
onInteractWithout: () => void
}) {
const t = useTheme()
const {t: l} = useLingui()
@@ -63,7 +67,13 @@ export function PublicationMetaRow({
<InlineLinkText
label={l`View @${handle}'s profile`}
to={makeProfileLink({did: author.did, handle})}
style={metaTextStyle}>
style={metaTextStyle}
onPress={e => {
// this link is nested, yes it's not ideal
e.stopPropagation()
}}
onMouseEnter={onInteractWithin}
onMouseLeave={onInteractWithout}>
@{handle}
</InlineLinkText>
</Trans>
@@ -18,6 +18,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useBreakpoints, useTheme, utils} from '#/alf'
import {ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock'
import {Leaflet} from '#/components/icons/community/Leaflet'
import {Offprint} from '#/components/icons/community/Offprint'
@@ -309,8 +310,10 @@ export const StandardSiteEmbed = ({
</Link>
{view.source && (
<View style={[a.px_md]}>
<Divider />
<>
<View style={[a.px_md]}>
<Divider />
</View>
<PublicationFooter
view={view}
onPress={onPress}
@@ -318,7 +321,7 @@ export const StandardSiteEmbed = ({
themeColors={themeColors}
author={{did: maybeAuthorDid}}
/>
</View>
</>
)}
</View>
)
@@ -344,6 +347,11 @@ export function PublicationCard({
const t = useTheme()
const {t: l} = useLingui()
const {gtPhone} = useBreakpoints()
const {
state: interactedWithin,
onIn: onInteractWithin,
onOut: onInteractWithout,
} = useInteractionState()
if (!view.source) return null
@@ -354,77 +362,85 @@ export function PublicationCard({
label={l`Subscribe`}
onPress={onPress}
onLongPress={onLongPress}>
{({hovered}) => (
<View
style={[
a.flex_col,
a.rounded_md,
a.overflow_hidden,
a.w_full,
a.border,
a.p_md,
t.atoms.border_contrast_low,
style,
]}>
{({hovered: maybeHovered}) => {
const hovered = maybeHovered && !interactedWithin
return (
<View
style={[
a.flex_1,
a.align_center,
a.justify_between,
a.gap_md,
gtPhone && [a.flex_row, a.gap_sm],
]}
testID="publication-embed-footer">
a.flex_col,
a.rounded_md,
a.overflow_hidden,
a.w_full,
a.border,
a.p_md,
t.atoms.border_contrast_low,
style,
]}>
<View
style={[
a.w_full,
a.flex_row,
a.flex_1,
a.align_center,
a.gap_sm,
gtPhone && a.flex_1,
]}>
<>
<PublicationIcon
a.justify_between,
a.gap_md,
gtPhone && [a.flex_row, a.gap_sm],
]}
testID="publication-embed-footer">
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.gap_sm,
gtPhone && a.flex_1,
]}>
<>
<PublicationIcon
view={view}
size={40}
hovered={hovered}
themeColors={themeColors}
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
numberOfLines={1}
style={[
a.text_md,
a.font_semi_bold,
t.atoms.text,
hovered && a.underline,
]}>
{view.source?.title}
</Text>
<PublicationMetaRow
view={view}
author={author}
onInteractWithin={onInteractWithin}
onInteractWithout={onInteractWithout}
/>
</View>
</>
</View>
{!hideSubscribe && (
<SubscribeButton
view={view}
size={40}
hovered={hovered}
themeColors={themeColors}
style={[!gtPhone && [a.w_full, a.justify_center]]}
onPress={onPress}
onLongPress={onLongPress}
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
numberOfLines={1}
style={[
a.text_md,
a.font_semi_bold,
t.atoms.text,
hovered && a.underline,
]}>
{view.source?.title}
</Text>
<PublicationMetaRow view={view} author={author} />
</View>
</>
)}
</View>
{!hideSubscribe && (
<SubscribeButton
view={view}
style={[!gtPhone && [a.w_full, a.justify_center]]}
onPress={onPress}
onLongPress={onLongPress}
/>
{view.description && (
<View style={[a.pt_sm]}>
<Text style={[a.text_sm, a.leading_snug]} numberOfLines={3}>
{view.description}
</Text>
</View>
)}
</View>
{view.description && (
<View style={[a.pt_sm]}>
<Text style={[a.text_sm, a.leading_snug]} numberOfLines={3}>
{view.description}
</Text>
</View>
)}
</View>
)}
)
}}
</Link>
)
}
@@ -548,6 +564,17 @@ export function PublicationFooter({
const t = useTheme()
const {t: l} = useLingui()
const {gtPhone} = useBreakpoints()
const {
state: maybeHovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const {
state: interactedWithin,
onIn: onInteractWithin,
onOut: onInteractWithout,
} = useInteractionState()
const hovered = maybeHovered && !interactedWithin
if (!view.source) return null
@@ -557,11 +584,14 @@ export function PublicationFooter({
a.flex_1,
a.align_center,
a.justify_between,
a.py_md,
a.p_md,
a.gap_md,
gtPhone && [a.flex_row, a.gap_sm],
]}
testID="publication-embed-footer">
testID="publication-embed-footer"
// @ts-ignore it's Fine™
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}>
<Link
shouldProxy
to={view.source.uri}
@@ -575,29 +605,32 @@ export function PublicationFooter({
a.gap_sm,
gtPhone && a.flex_1,
]}>
{({hovered}) => (
<>
<PublicationIcon
<>
<PublicationIcon
view={view}
size={32}
hovered={hovered}
themeColors={themeColors}
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
numberOfLines={1}
style={[
a.text_sm,
a.font_medium,
t.atoms.text,
hovered && a.underline,
]}>
{view.source?.title}
</Text>
<PublicationMetaRow
view={view}
size={32}
hovered={hovered}
themeColors={themeColors}
author={author}
onInteractWithin={onInteractWithin}
onInteractWithout={onInteractWithout}
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
numberOfLines={1}
style={[
a.text_sm,
a.font_medium,
t.atoms.text,
hovered && a.underline,
]}>
{view.source?.title}
</Text>
<PublicationMetaRow view={view} author={author} />
</View>
</>
)}
</View>
</>
</Link>
{!hideSubscribe && (