make standardsiteembed peekable

This commit is contained in:
Samuel Newman
2026-06-11 14:17:57 +03:00
parent 0187a49534
commit 21b49a301e
3 changed files with 125 additions and 98 deletions
-3
View File
@@ -976,9 +976,6 @@
}
},
"src/lib/hooks/useOpenLink.ts": {
"@typescript-eslint/no-floating-promises": {
"count": 2
},
"@typescript-eslint/require-await": {
"count": 1
}
@@ -127,7 +127,6 @@ export const StandardSiteEmbed = ({
style={[
a.flex_col,
a.rounded_lg,
a.overflow_hidden,
a.w_full,
a.border,
t.atoms.bg,
@@ -135,126 +134,149 @@ export const StandardSiteEmbed = ({
preview && a.pointer_events_none,
style,
]}>
{/*
* The article is an in-flow `Link` wrapping the content so the iOS peek
* menu has something to lift. The publication footer below lives outside
* it - it has its own link (to the publication) and Subscribe button, so
* it can't be nested inside the article's peek.
*/}
<Link
peek
shouldProxy
to={view.uri}
label={view.title || l`Open link to ${niceUrl}`}
onPress={onPress}
onLongPress={onLongPress}
style={[a.absolute, a.inset_0, a.z_10]}
style={[a.rounded_lg]}
{...web({
onMouseEnter: onInteract,
onMouseLeave: onInteractOut,
})}
onFocus={onInteract}
onBlur={onInteractOut}>
<></>
</Link>
<View
style={[
a.w_full,
a.z_10,
a.pointer_events_none,
interacted && [t.atoms.bg_contrast_25],
]}>
{imageUri ? (
<Image
style={[a.aspect_card]}
source={{uri: imageUri}}
accessibilityIgnoresInvertColors
loading="lazy"
useAppleWebpCodec
/>
) : undefined}
<View
style={[
a.flex_1,
a.pt_sm,
hasMedia && a.border_t,
interacted
? t.atoms.border_contrast_high
: t.atoms.border_contrast_low,
{gap: 3},
isStandard && a.pt_md,
]}>
{() => (
<View
style={[
a.pb_xs,
a.px_md,
{gap: 3},
isStandard && [{gap: 5}, a.pb_sm],
a.w_full,
a.overflow_hidden,
// The container can't clip (overflow_hidden there breaks the peek
// lift animation), so each half rounds its own outer corners to
// match the container border. The article rounds the top, plus the
// bottom when there's no footer beneath it.
{
borderTopLeftRadius: a.rounded_lg.borderRadius,
borderTopRightRadius: a.rounded_lg.borderRadius,
},
!view.source && {
borderBottomLeftRadius: a.rounded_lg.borderRadius,
borderBottomRightRadius: a.rounded_lg.borderRadius,
},
interacted ? t.atoms.bg_contrast_25 : t.atoms.bg,
]}>
<Text
emoji
numberOfLines={3}
style={[
a.text_md,
a.font_semi_bold,
a.leading_snug,
isStandard && [a.text_lg, a.font_bold],
]}>
{view.title}
</Text>
{view.description ? (
<Text
emoji
numberOfLines={view.thumb ? 2 : 4}
style={[a.text_sm, a.leading_snug]}>
{view.description}
</Text>
{imageUri ? (
<Image
style={[a.aspect_card]}
source={{uri: imageUri}}
accessibilityIgnoresInvertColors
loading="lazy"
useAppleWebpCodec
/>
) : undefined}
{isStandard && (view.createdAt || view.readingTime) && (
<View
style={[
a.flex_1,
a.pt_sm,
hasMedia && a.border_t,
interacted
? t.atoms.border_contrast_high
: t.atoms.border_contrast_low,
{gap: 3},
isStandard && a.pt_md,
]}>
<View
style={[a.flex_row, a.align_center, a.gap_md, {paddingTop: 2}]}>
{view.createdAt && (
style={[
a.pb_xs,
a.px_md,
{gap: 3},
isStandard && [{gap: 5}, a.pb_sm],
]}>
<Text
emoji
numberOfLines={3}
style={[
a.text_md,
a.font_semi_bold,
a.leading_snug,
isStandard && [a.text_lg, a.font_bold],
]}>
{view.title}
</Text>
{view.description ? (
<Text
style={[
a.text_xs,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
{niceDate(i18n, view.createdAt, 'long', 'none')}
emoji
numberOfLines={view.thumb ? 2 : 4}
style={[a.text_sm, a.leading_snug]}>
{view.description}
</Text>
)}
{view.readingTime && (
<View style={[a.flex_row, a.align_center, a.gap_2xs]}>
<Clock size="xs" style={t.atoms.text_contrast_medium} />
<Text
style={[
a.text_xs,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
{l({
message: plural(view.readingTime, {
one: '#m',
other: '#m',
}),
comment: `How long it takes to read an article, in minutes. Displayed in a short form, e.g. "5m" for 5 minutes.`,
})}
</Text>
) : undefined}
{isStandard && (view.createdAt || view.readingTime) && (
<View
style={[
a.flex_row,
a.align_center,
a.gap_md,
{paddingTop: 2},
]}>
{view.createdAt && (
<Text
style={[
a.text_xs,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
{niceDate(i18n, view.createdAt, 'long', 'none')}
</Text>
)}
{view.readingTime && (
<View style={[a.flex_row, a.align_center, a.gap_2xs]}>
<Clock size="xs" style={t.atoms.text_contrast_medium} />
<Text
style={[
a.text_xs,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
{l({
message: plural(view.readingTime, {
one: '#m',
other: '#m',
}),
comment: `How long it takes to read an article, in minutes. Displayed in a short form, e.g. "5m" for 5 minutes.`,
})}
</Text>
</View>
)}
</View>
)}
</View>
</View>
{!view.source && (
<View style={[a.px_md]}>
<Divider />
<View style={[a.py_sm]}>
<StandardSiteMetaRow preview={preview} view={view} />
</View>
</View>
)}
</View>
</View>
{!view.source && (
<View style={[a.px_md]}>
<Divider />
<View style={[a.py_sm]}>
<StandardSiteMetaRow preview={preview} view={view} />
</View>
</View>
)}
</View>
</Link>
{view.source && (
<View style={[a.z_20]}>
<View>
<Divider />
<PublicationFooter
preview={preview}
@@ -629,6 +651,14 @@ export function PublicationFooter({
a.justify_between,
a.p_md,
a.gap_md,
a.overflow_hidden,
// Rounds the bottom corners to match the container border, since the
// container itself can't clip (overflow_hidden there breaks the peek
// lift animation on the article above).
{
borderBottomLeftRadius: a.rounded_lg.borderRadius,
borderBottomRightRadius: a.rounded_lg.borderRadius,
},
gtPhone && [a.flex_row, a.gap_sm],
interactedOuter && t.atoms.bg_contrast_25,
preview && a.pointer_events_none,
+2 -2
View File
@@ -66,12 +66,12 @@ export function useOpenLink() {
}).catch(err => {
if (__DEV__)
logger.error('Could not open web browser', {message: err})
Linking.openURL(url)
void Linking.openURL(url)
})
return
}
}
Linking.openURL(url)
void Linking.openURL(url)
},
[ax, enabled, inAppBrowserConsentControl, t, dialogContext],
)