Improve byline linking and interaction states
This commit is contained in:
+10
-2
@@ -326,6 +326,8 @@ export type InlineLinkProps = React.PropsWithChildren<
|
|||||||
disableUnderline?: boolean
|
disableUnderline?: boolean
|
||||||
title?: TextProps['title']
|
title?: TextProps['title']
|
||||||
overridePresentation?: boolean
|
overridePresentation?: boolean
|
||||||
|
onMouseEnter?: () => void
|
||||||
|
onMouseLeave?: () => void
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -388,8 +390,14 @@ export function InlineLinkText({
|
|||||||
role="link"
|
role="link"
|
||||||
onPress={download ? undefined : onPress}
|
onPress={download ? undefined : onPress}
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
onMouseEnter={onHoverIn}
|
onMouseEnter={() => {
|
||||||
onMouseLeave={onHoverOut}
|
rest.onMouseEnter?.()
|
||||||
|
onHoverIn()
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
rest.onMouseLeave?.()
|
||||||
|
onHoverOut()
|
||||||
|
}}
|
||||||
accessibilityRole="link"
|
accessibilityRole="link"
|
||||||
href={href}
|
href={href}
|
||||||
{...web({
|
{...web({
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ import {Text} from '#/components/Typography'
|
|||||||
export function PublicationMetaRow({
|
export function PublicationMetaRow({
|
||||||
view,
|
view,
|
||||||
author,
|
author,
|
||||||
|
onInteractWithin,
|
||||||
|
onInteractWithout,
|
||||||
}: {
|
}: {
|
||||||
view: AppBskyEmbedExternal.ViewExternal
|
view: AppBskyEmbedExternal.ViewExternal
|
||||||
author: {did: string | null | undefined}
|
author: {did: string | null | undefined}
|
||||||
|
onInteractWithin: () => void
|
||||||
|
onInteractWithout: () => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
@@ -63,7 +67,13 @@ export function PublicationMetaRow({
|
|||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
label={l`View @${handle}'s profile`}
|
label={l`View @${handle}'s profile`}
|
||||||
to={makeProfileLink({did: author.did, handle})}
|
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}
|
@{handle}
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
</Trans>
|
</Trans>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|||||||
import {atoms as a, useBreakpoints, useTheme, utils} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, utils} from '#/alf'
|
||||||
import {ButtonIcon, ButtonText} from '#/components/Button'
|
import {ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock'
|
import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock'
|
||||||
import {Leaflet} from '#/components/icons/community/Leaflet'
|
import {Leaflet} from '#/components/icons/community/Leaflet'
|
||||||
import {Offprint} from '#/components/icons/community/Offprint'
|
import {Offprint} from '#/components/icons/community/Offprint'
|
||||||
@@ -309,8 +310,10 @@ export const StandardSiteEmbed = ({
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{view.source && (
|
{view.source && (
|
||||||
<View style={[a.px_md]}>
|
<>
|
||||||
<Divider />
|
<View style={[a.px_md]}>
|
||||||
|
<Divider />
|
||||||
|
</View>
|
||||||
<PublicationFooter
|
<PublicationFooter
|
||||||
view={view}
|
view={view}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -318,7 +321,7 @@ export const StandardSiteEmbed = ({
|
|||||||
themeColors={themeColors}
|
themeColors={themeColors}
|
||||||
author={{did: maybeAuthorDid}}
|
author={{did: maybeAuthorDid}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -344,6 +347,11 @@ export function PublicationCard({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {gtPhone} = useBreakpoints()
|
const {gtPhone} = useBreakpoints()
|
||||||
|
const {
|
||||||
|
state: interactedWithin,
|
||||||
|
onIn: onInteractWithin,
|
||||||
|
onOut: onInteractWithout,
|
||||||
|
} = useInteractionState()
|
||||||
|
|
||||||
if (!view.source) return null
|
if (!view.source) return null
|
||||||
|
|
||||||
@@ -354,77 +362,85 @@ export function PublicationCard({
|
|||||||
label={l`Subscribe`}
|
label={l`Subscribe`}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
onLongPress={onLongPress}>
|
onLongPress={onLongPress}>
|
||||||
{({hovered}) => (
|
{({hovered: maybeHovered}) => {
|
||||||
<View
|
const hovered = maybeHovered && !interactedWithin
|
||||||
style={[
|
return (
|
||||||
a.flex_col,
|
|
||||||
a.rounded_md,
|
|
||||||
a.overflow_hidden,
|
|
||||||
a.w_full,
|
|
||||||
a.border,
|
|
||||||
a.p_md,
|
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
style,
|
|
||||||
]}>
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_col,
|
||||||
a.align_center,
|
a.rounded_md,
|
||||||
a.justify_between,
|
a.overflow_hidden,
|
||||||
a.gap_md,
|
a.w_full,
|
||||||
gtPhone && [a.flex_row, a.gap_sm],
|
a.border,
|
||||||
]}
|
a.p_md,
|
||||||
testID="publication-embed-footer">
|
t.atoms.border_contrast_low,
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.w_full,
|
a.flex_1,
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.gap_sm,
|
a.justify_between,
|
||||||
gtPhone && a.flex_1,
|
a.gap_md,
|
||||||
]}>
|
gtPhone && [a.flex_row, a.gap_sm],
|
||||||
<>
|
]}
|
||||||
<PublicationIcon
|
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}
|
view={view}
|
||||||
size={40}
|
style={[!gtPhone && [a.w_full, a.justify_center]]}
|
||||||
hovered={hovered}
|
onPress={onPress}
|
||||||
themeColors={themeColors}
|
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>
|
</View>
|
||||||
|
|
||||||
{!hideSubscribe && (
|
{view.description && (
|
||||||
<SubscribeButton
|
<View style={[a.pt_sm]}>
|
||||||
view={view}
|
<Text style={[a.text_sm, a.leading_snug]} numberOfLines={3}>
|
||||||
style={[!gtPhone && [a.w_full, a.justify_center]]}
|
{view.description}
|
||||||
onPress={onPress}
|
</Text>
|
||||||
onLongPress={onLongPress}
|
</View>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</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>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -548,6 +564,17 @@ export function PublicationFooter({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {gtPhone} = useBreakpoints()
|
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
|
if (!view.source) return null
|
||||||
|
|
||||||
@@ -557,11 +584,14 @@ export function PublicationFooter({
|
|||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.justify_between,
|
a.justify_between,
|
||||||
a.py_md,
|
a.p_md,
|
||||||
a.gap_md,
|
a.gap_md,
|
||||||
gtPhone && [a.flex_row, a.gap_sm],
|
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
|
<Link
|
||||||
shouldProxy
|
shouldProxy
|
||||||
to={view.source.uri}
|
to={view.source.uri}
|
||||||
@@ -575,29 +605,32 @@ export function PublicationFooter({
|
|||||||
a.gap_sm,
|
a.gap_sm,
|
||||||
gtPhone && a.flex_1,
|
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}
|
view={view}
|
||||||
size={32}
|
author={author}
|
||||||
hovered={hovered}
|
onInteractWithin={onInteractWithin}
|
||||||
themeColors={themeColors}
|
onInteractWithout={onInteractWithout}
|
||||||
/>
|
/>
|
||||||
<View style={[a.flex_1, a.gap_2xs]}>
|
</View>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{!hideSubscribe && (
|
{!hideSubscribe && (
|
||||||
|
|||||||
Reference in New Issue
Block a user