Replace "Note about sharing" prompt with an inline hint (#8452)

* add pwi warning to share menu, remove prompt

* add pwi label to web, remove prompt

* add an option to the PWI menu

* conditionally reorder items on web
This commit is contained in:
Samuel Newman
2025-06-06 18:21:23 +03:00
committed by GitHub
parent 23a7bc50db
commit 487da69a15
5 changed files with 143 additions and 147 deletions
+28 -17
View File
@@ -1,5 +1,11 @@
import React from 'react' import {cloneElement, Fragment, isValidElement, useMemo} from 'react'
import {Pressable, StyleProp, View, ViewStyle} from 'react-native' import {
Pressable,
type StyleProp,
type TextStyle,
View,
type ViewStyle,
} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import flattenReactChildren from 'react-keyed-flatten-children' import flattenReactChildren from 'react-keyed-flatten-children'
@@ -16,12 +22,12 @@ import {
useMenuItemContext, useMenuItemContext,
} from '#/components/Menu/context' } from '#/components/Menu/context'
import { import {
ContextType, type ContextType,
GroupProps, type GroupProps,
ItemIconProps, type ItemIconProps,
ItemProps, type ItemProps,
ItemTextProps, type ItemTextProps,
TriggerProps, type TriggerProps,
} from '#/components/Menu/types' } from '#/components/Menu/types'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -39,7 +45,7 @@ export function Root({
control?: Dialog.DialogControlProps control?: Dialog.DialogControlProps
}>) { }>) {
const defaultControl = Dialog.useDialogControl() const defaultControl = Dialog.useDialogControl()
const context = React.useMemo<ContextType>( const context = useMemo<ContextType>(
() => ({ () => ({
control: control || defaultControl, control: control || defaultControl,
}), }),
@@ -276,16 +282,21 @@ export function ContainerItem({
) )
} }
export function LabelText({children}: {children: React.ReactNode}) { export function LabelText({
children,
style,
}: {
children: React.ReactNode
style?: StyleProp<TextStyle>
}) {
const t = useTheme() const t = useTheme()
return ( return (
<Text <Text
style={[ style={[
a.font_bold, a.font_bold,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
{ {marginBottom: -8},
marginBottom: -8, style,
},
]}> ]}>
{children} {children}
</Text> </Text>
@@ -304,20 +315,20 @@ export function Group({children, style}: GroupProps) {
style, style,
]}> ]}>
{flattenReactChildren(children).map((child, i) => { {flattenReactChildren(children).map((child, i) => {
return React.isValidElement(child) && return isValidElement(child) &&
(child.type === Item || child.type === ContainerItem) ? ( (child.type === Item || child.type === ContainerItem) ? (
<React.Fragment key={i}> <Fragment key={i}>
{i > 0 ? ( {i > 0 ? (
<View style={[a.border_b, t.atoms.border_contrast_low]} /> <View style={[a.border_b, t.atoms.border_contrast_low]} />
) : null} ) : null}
{React.cloneElement(child, { {cloneElement(child, {
// @ts-expect-error cloneElement is not aware of the types // @ts-expect-error cloneElement is not aware of the types
style: { style: {
borderRadius: 0, borderRadius: 0,
borderWidth: 0, borderWidth: 0,
}, },
})} })}
</React.Fragment> </Fragment>
) : null ) : null
})} })}
</View> </View>
+25 -14
View File
@@ -1,5 +1,11 @@
import React from 'react' import {forwardRef, useCallback, useId, useMemo, useState} from 'react'
import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {
Pressable,
type StyleProp,
type TextStyle,
View,
type ViewStyle,
} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {DropdownMenu} from 'radix-ui' import {DropdownMenu} from 'radix-ui'
@@ -29,10 +35,10 @@ import {Text} from '#/components/Typography'
export {useMenuContext} export {useMenuContext}
export function useMenuControl(): Dialog.DialogControlProps { export function useMenuControl(): Dialog.DialogControlProps {
const id = React.useId() const id = useId()
const [isOpen, setIsOpen] = React.useState(false) const [isOpen, setIsOpen] = useState(false)
return React.useMemo( return useMemo(
() => ({ () => ({
id, id,
ref: {current: null}, ref: {current: null},
@@ -56,13 +62,13 @@ export function Root({
}>) { }>) {
const {_} = useLingui() const {_} = useLingui()
const defaultControl = useMenuControl() const defaultControl = useMenuControl()
const context = React.useMemo<ContextType>( const context = useMemo<ContextType>(
() => ({ () => ({
control: control || defaultControl, control: control || defaultControl,
}), }),
[control, defaultControl], [control, defaultControl],
) )
const onOpenChange = React.useCallback( const onOpenChange = useCallback(
(open: boolean) => { (open: boolean) => {
if (context.control.isOpen && !open) { if (context.control.isOpen && !open) {
context.control.close() context.control.close()
@@ -96,7 +102,7 @@ export function Root({
) )
} }
const RadixTriggerPassThrough = React.forwardRef( const RadixTriggerPassThrough = forwardRef(
( (
props: { props: {
children: ( children: (
@@ -355,18 +361,23 @@ export function ItemRadio({selected}: {selected: boolean}) {
) )
} }
export function LabelText({children}: {children: React.ReactNode}) { export function LabelText({
children,
style,
}: {
children: React.ReactNode
style?: StyleProp<TextStyle>
}) {
const t = useTheme() const t = useTheme()
return ( return (
<Text <Text
style={[ style={[
a.font_bold, a.font_bold,
a.pt_md, a.p_sm,
a.pb_sm,
t.atoms.text_contrast_low, t.atoms.text_contrast_low,
{ a.leading_snug,
paddingHorizontal: 10, {paddingHorizontal: 10},
}, style,
]}> ]}>
{children} {children}
</Text> </Text>
@@ -48,7 +48,7 @@ import {
useProfileMuteMutationQueue, useProfileMuteMutationQueue,
} from '#/state/queries/profile' } from '#/state/queries/profile'
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
import {useSession} from '#/state/session' import {useRequireAuth, useSession} from '#/state/session'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
@@ -113,6 +113,7 @@ let PostMenuItems = ({
const {mutateAsync: deletePostMutate} = usePostDeleteMutation() const {mutateAsync: deletePostMutate} = usePostDeleteMutation()
const {mutateAsync: pinPostMutate, isPending: isPinPending} = const {mutateAsync: pinPostMutate, isPending: isPinPending} =
usePinnedPostMutation() usePinnedPostMutation()
const requireSignIn = useRequireAuth()
const hiddenPosts = useHiddenPosts() const hiddenPosts = useHiddenPosts()
const {hidePost} = useHiddenPostsApi() const {hidePost} = useHiddenPostsApi()
const feedFeedback = useFeedFeedbackContext() const feedFeedback = useFeedFeedbackContext()
@@ -397,6 +398,8 @@ let PostMenuItems = ({
openLink(url) openLink(url)
} }
const onSignIn = () => requireSignIn(() => {})
const gate = useGate() const gate = useGate()
const isDiscoverDebugUser = const isDiscoverDebugUser =
IS_INTERNAL || IS_INTERNAL ||
@@ -434,7 +437,7 @@ let PostMenuItems = ({
)} )}
<Menu.Group> <Menu.Group>
{(!hideInPWI || hasSession) && ( {!hideInPWI || hasSession ? (
<> <>
<Menu.Item <Menu.Item
testID="postDropdownTranslateBtn" testID="postDropdownTranslateBtn"
@@ -452,6 +455,14 @@ let PostMenuItems = ({
<Menu.ItemIcon icon={ClipboardIcon} position="right" /> <Menu.ItemIcon icon={ClipboardIcon} position="right" />
</Menu.Item> </Menu.Item>
</> </>
) : (
<Menu.Item
testID="postDropdownSignInBtn"
label={_(msg`Sign in to view post`)}
onPress={onSignIn}>
<Menu.ItemText>{_(msg`Sign in to view post`)}</Menu.ItemText>
<Menu.ItemIcon icon={Eye} position="right" />
</Menu.Item>
)} )}
</Menu.Group> </Menu.Group>
@@ -14,6 +14,8 @@ import {isIOS} from '#/platform/detection'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox' import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
@@ -21,7 +23,6 @@ import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/i
import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {useDevMode} from '#/storage/hooks/dev-mode' import {useDevMode} from '#/storage/hooks/dev-mode'
import {RecentChats} from './RecentChats' import {RecentChats} from './RecentChats'
import {type ShareMenuItemsProps} from './ShareMenuItems.types' import {type ShareMenuItemsProps} from './ShareMenuItems.types'
@@ -30,11 +31,9 @@ let ShareMenuItems = ({
post, post,
onShare: onShareProp, onShare: onShareProp,
}: ShareMenuItemsProps): React.ReactNode => { }: ShareMenuItemsProps): React.ReactNode => {
const {hasSession, currentAccount} = useSession() const {hasSession} = useSession()
const {_} = useLingui() const {_} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const pwiWarningShareControl = useDialogControl()
const pwiWarningCopyControl = useDialogControl()
const sendViaChatControl = useDialogControl() const sendViaChatControl = useDialogControl()
const [devModeEnabled] = useDevMode() const [devModeEnabled] = useDevMode()
@@ -52,9 +51,6 @@ let ShareMenuItems = ({
) )
}, [postAuthor]) }, [postAuthor])
const showLoggedOutWarning =
postAuthor.did !== currentAccount?.did && hideInPWI
const onSharePost = () => { const onSharePost = () => {
logger.metric('share:press:nativeShare', {}, {statsig: true}) logger.metric('share:press:nativeShare', {}, {statsig: true})
const url = toShareUrl(href) const url = toShareUrl(href)
@@ -117,13 +113,7 @@ let ShareMenuItems = ({
<Menu.Item <Menu.Item
testID="postDropdownShareBtn" testID="postDropdownShareBtn"
label={_(msg`Share via...`)} label={_(msg`Share via...`)}
onPress={() => { onPress={onSharePost}>
if (showLoggedOutWarning) {
pwiWarningShareControl.open()
} else {
onSharePost()
}
}}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Share via...</Trans> <Trans>Share via...</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -133,13 +123,7 @@ let ShareMenuItems = ({
<Menu.Item <Menu.Item
testID="postDropdownShareBtn" testID="postDropdownShareBtn"
label={_(msg`Copy link to post`)} label={_(msg`Copy link to post`)}
onPress={() => { onPress={onCopyLink}>
if (showLoggedOutWarning) {
pwiWarningCopyControl.open()
} else {
onCopyLink()
}
}}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Copy link to post</Trans> <Trans>Copy link to post</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -147,6 +131,16 @@ let ShareMenuItems = ({
</Menu.Item> </Menu.Item>
</Menu.Group> </Menu.Group>
{hideInPWI && (
<Menu.Group>
<Menu.ContainerItem>
<Admonition type="warning" style={[a.flex_1, a.border_0, a.p_0]}>
<Trans>This post is only visible to logged-in users.</Trans>
</Admonition>
</Menu.ContainerItem>
</Menu.Group>
)}
{devModeEnabled && ( {devModeEnabled && (
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
@@ -171,26 +165,6 @@ let ShareMenuItems = ({
)} )}
</Menu.Outer> </Menu.Outer>
<Prompt.Basic
control={pwiWarningShareControl}
title={_(msg`Note about sharing`)}
description={_(
msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
)}
onConfirm={onSharePost}
confirmButtonCta={_(msg`Share anyway`)}
/>
<Prompt.Basic
control={pwiWarningCopyControl}
title={_(msg`Note about sharing`)}
description={_(
msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
)}
onConfirm={onCopyLink}
confirmButtonCta={_(msg`Copy anyway`)}
/>
<SendViaChatDialog <SendViaChatDialog
control={sendViaChatControl} control={sendViaChatControl}
onSelectChat={onSelectChatToShareTo} onSelectChat={onSelectChatToShareTo}
@@ -22,7 +22,6 @@ import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/i
import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {useDevMode} from '#/storage/hooks/dev-mode' import {useDevMode} from '#/storage/hooks/dev-mode'
import {type ShareMenuItemsProps} from './ShareMenuItems.types' import {type ShareMenuItemsProps} from './ShareMenuItems.types'
@@ -32,11 +31,10 @@ let ShareMenuItems = ({
timestamp, timestamp,
onShare: onShareProp, onShare: onShareProp,
}: ShareMenuItemsProps): React.ReactNode => { }: ShareMenuItemsProps): React.ReactNode => {
const {hasSession, currentAccount} = useSession() const {hasSession} = useSession()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {_} = useLingui() const {_} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const loggedOutWarningPromptControl = useDialogControl()
const embedPostControl = useDialogControl() const embedPostControl = useDialogControl()
const sendViaChatControl = useDialogControl() const sendViaChatControl = useDialogControl()
const [devModeEnabled] = useDevMode() const [devModeEnabled] = useDevMode()
@@ -56,9 +54,6 @@ let ShareMenuItems = ({
) )
}, [postAuthor]) }, [postAuthor])
const showLoggedOutWarning =
postAuthor.did !== currentAccount?.did && hideInPWI
const onCopyLink = () => { const onCopyLink = () => {
logger.metric('share:press:copyLink', {}, {statsig: true}) logger.metric('share:press:copyLink', {}, {statsig: true})
const url = toShareUrl(href) const url = toShareUrl(href)
@@ -84,92 +79,86 @@ let ShareMenuItems = ({
shareText(postAuthor.did) shareText(postAuthor.did)
} }
const copyLinkItem = (
<Menu.Item
testID="postDropdownShareBtn"
label={_(msg`Copy link to post`)}
onPress={onCopyLink}>
<Menu.ItemText>
<Trans>Copy link to post</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={ChainLinkIcon} position="right" />
</Menu.Item>
)
return ( return (
<> <>
<Menu.Outer> <Menu.Outer>
<Menu.Group> {!hideInPWI && copyLinkItem}
{hasSession && (
<Menu.Item <Menu.Item
testID="postDropdownShareBtn" testID="postDropdownSendViaDMBtn"
label={_(msg`Copy link to post`)} label={_(msg`Send via direct message`)}
onPress={() => { onPress={() => {
if (showLoggedOutWarning) { logger.metric('share:press:openDmSearch', {}, {statsig: true})
loggedOutWarningPromptControl.open() sendViaChatControl.open()
} else {
onCopyLink()
}
}}> }}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Copy link to post</Trans> <Trans>Send via direct message</Trans>
</Menu.ItemText> </Menu.ItemText>
<Menu.ItemIcon icon={ChainLinkIcon} position="right" /> <Menu.ItemIcon icon={Send} position="right" />
</Menu.Item> </Menu.Item>
)}
{hasSession && ( {canEmbed && (
<Menu.Item <Menu.Item
testID="postDropdownSendViaDMBtn" testID="postDropdownEmbedBtn"
label={_(msg`Send via direct message`)} label={_(msg`Embed post`)}
onPress={() => { onPress={() => {
logger.metric('share:press:openDmSearch', {}, {statsig: true}) logger.metric('share:press:embed', {}, {statsig: true})
sendViaChatControl.open() embedPostControl.open()
}}> }}>
<Menu.ItemText> <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
<Trans>Send via direct message</Trans> <Menu.ItemIcon icon={CodeBracketsIcon} position="right" />
</Menu.ItemText> </Menu.Item>
<Menu.ItemIcon icon={Send} position="right" /> )}
</Menu.Item>
)}
{canEmbed && ( {hideInPWI && (
<Menu.Item <>
testID="postDropdownEmbedBtn" {hasSession && <Menu.Divider />}
label={_(msg`Embed post`)} {copyLinkItem}
onPress={() => { <Menu.LabelText style={{maxWidth: 220}}>
logger.metric('share:press:embed', {}, {statsig: true}) <Trans>Note: This post is only visible to logged-in users.</Trans>
embedPostControl.open() </Menu.LabelText>
}}> </>
<Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> )}
<Menu.ItemIcon icon={CodeBracketsIcon} position="right" />
</Menu.Item>
)}
</Menu.Group>
{devModeEnabled && ( {devModeEnabled && (
<> <>
<Menu.Divider /> <Menu.Divider />
<Menu.Group> <Menu.Item
<Menu.Item testID="postAtUriShareBtn"
testID="postAtUriShareBtn" label={_(msg`Copy post at:// URI`)}
label={_(msg`Copy post at:// URI`)} onPress={onShareATURI}>
onPress={onShareATURI}> <Menu.ItemText>
<Menu.ItemText> <Trans>Copy post at:// URI</Trans>
<Trans>Copy post at:// URI</Trans> </Menu.ItemText>
</Menu.ItemText> <Menu.ItemIcon icon={ClipboardIcon} position="right" />
<Menu.ItemIcon icon={ClipboardIcon} position="right" /> </Menu.Item>
</Menu.Item> <Menu.Item
<Menu.Item testID="postAuthorDIDShareBtn"
testID="postAuthorDIDShareBtn" label={_(msg`Copy author DID`)}
label={_(msg`Copy author DID`)} onPress={onShareAuthorDID}>
onPress={onShareAuthorDID}> <Menu.ItemText>
<Menu.ItemText> <Trans>Copy author DID</Trans>
<Trans>Copy author DID</Trans> </Menu.ItemText>
</Menu.ItemText> <Menu.ItemIcon icon={ClipboardIcon} position="right" />
<Menu.ItemIcon icon={ClipboardIcon} position="right" /> </Menu.Item>
</Menu.Item>
</Menu.Group>
</> </>
)} )}
</Menu.Outer> </Menu.Outer>
<Prompt.Basic
control={loggedOutWarningPromptControl}
title={_(msg`Note about sharing`)}
description={_(
msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
)}
onConfirm={onCopyLink}
confirmButtonCta={_(msg`Share anyway`)}
/>
{canEmbed && ( {canEmbed && (
<EmbedDialog <EmbedDialog
control={embedPostControl} control={embedPostControl}