diff --git a/src/components/BottomSheetButton.tsx b/src/components/BottomSheetButton.tsx
new file mode 100644
index 0000000000..9eaee55deb
--- /dev/null
+++ b/src/components/BottomSheetButton.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+
+import {Button, ButtonProps} from '#/components/Button'
+import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
+
+export function BottomSheetButton({children, ...rest}: ButtonProps) {
+ return (
+
+ )
+}
diff --git a/src/components/BottomSheetLink.tsx b/src/components/BottomSheetLink.tsx
new file mode 100644
index 0000000000..36d52c90c1
--- /dev/null
+++ b/src/components/BottomSheetLink.tsx
@@ -0,0 +1,92 @@
+import React from 'react'
+import {StackActions, useNavigation} from '@react-navigation/native'
+
+import {NavigationProp} from '#/lib/routes/types'
+import {flatten, useTheme} from '#/alf'
+import {useDialogContext} from '#/components/Dialog'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+import {InlineLinkProps, useLink} from '#/components/Link'
+import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
+import {Text} from '#/components/Typography'
+import {router} from '#/routes'
+
+export function BottomSheetInlineLinkText({
+ children,
+ to,
+ action = 'push',
+ disableMismatchWarning,
+ style,
+ onPress: outerOnPress,
+ label,
+ shareOnLongPress,
+ disableUnderline,
+ ...rest
+}: InlineLinkProps) {
+ const t = useTheme()
+ const stringChildren = typeof children === 'string'
+ const navigation = useNavigation()
+ const dialog = useDialogContext()
+
+ const {href, isExternal, onLongPress} = useLink({
+ to,
+ displayText: stringChildren ? children : '',
+ action,
+ disableMismatchWarning,
+ onPress: outerOnPress,
+ shareOnLongPress,
+ })
+ const {
+ state: pressed,
+ onIn: onPressIn,
+ onOut: onPressOut,
+ } = useInteractionState()
+
+ const onPress = () => {
+ if (isExternal) {
+ return
+ }
+
+ dialog.close()
+
+ if (action === 'push') {
+ navigation.dispatch(StackActions.push(...router.matchPath(href)))
+ } else if (action === 'replace') {
+ navigation.dispatch(StackActions.replace(...router.matchPath(href)))
+ } else if (action === 'navigate') {
+ // @ts-ignore
+ navigation.navigate(...router.matchPath(href))
+ } else {
+ throw Error('Unsupported navigator action.')
+ }
+ }
+
+ const flattenedStyle = flatten(style) || {}
+
+ // eslint-disable-next-line bsky-internal/avoid-unwrapped-text
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/components/BottomSheetLink.web.tsx b/src/components/BottomSheetLink.web.tsx
new file mode 100644
index 0000000000..6bfb275c25
--- /dev/null
+++ b/src/components/BottomSheetLink.web.tsx
@@ -0,0 +1,3 @@
+import {Link as BottomSheetLink} from './Link'
+
+export {BottomSheetLink}
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 17179994a9..98ad1709c4 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -15,7 +15,9 @@ import {
import {LinearGradient} from 'expo-linear-gradient'
import {atoms as a, flatten, select, tokens, useTheme, web} from '#/alf'
+import {useDialogContext} from '#/components/Dialog'
import {Props as SVGIconProps} from '#/components/icons/common'
+import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {Text} from '#/components/Typography'
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
@@ -87,6 +89,7 @@ export type ButtonProps = Pick<
style?: StyleProp
hoverStyle?: StyleProp
children: NonTextElements | ((context: ButtonContext) => NonTextElements)
+ Component?: React.ComponentType
}
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
@@ -114,10 +117,22 @@ export const Button = React.forwardRef(
disabled = false,
style,
hoverStyle: hoverStyleProp,
+ Component,
...rest
},
ref,
) => {
+ // This will pick the correct default pressable to use. If we are inside a dialog, we need to use the RNGH
+ // pressable so that it is usable inside the dialog.
+ const {insideDialog} = useDialogContext()
+ if (!Component) {
+ if (insideDialog) {
+ Component = NormalizedRNGHPressable
+ } else {
+ Component = Pressable
+ }
+ }
+
const t = useTheme()
const [state, setState] = React.useState({
pressed: false,
@@ -449,10 +464,11 @@ export const Button = React.forwardRef(
const flattenedBaseStyles = flatten([baseStyles, style])
return (
- (
{typeof children === 'function' ? children(context) : children}
-
+
)
},
)
diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts
index 859f8edd77..27b8a56375 100644
--- a/src/components/Dialog/context.ts
+++ b/src/components/Dialog/context.ts
@@ -9,6 +9,7 @@ import {
export const Context = React.createContext({
close: () => {},
+ insideDialog: false,
})
export function useDialogContext() {
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 303da0d009..6fbc8d87a0 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -82,7 +82,7 @@ export function Outer({
[open, close],
)
- const context = React.useMemo(() => ({close}), [close])
+ const context = React.useMemo(() => ({close, insideDialog: true}), [close])
return (
diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts
index ed83bd02b1..a8568e5748 100644
--- a/src/components/Dialog/types.ts
+++ b/src/components/Dialog/types.ts
@@ -37,6 +37,7 @@ export type DialogControlProps = DialogControlRefProps & {
export type DialogContextProps = {
close: DialogControlProps['close']
+ insideDialog: boolean
}
export type DialogControlOpenOptions = {
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index c80b9f3707..56d030b5df 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -23,6 +23,7 @@ import {shouldClickOpenNewTab} from '#/platform/urls'
import {useModalControls} from '#/state/modals'
import {useOpenLink} from '#/state/preferences/in-app-browser'
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
+import {BottomSheetButton} from '#/components/BottomSheetButton'
import {Button, ButtonProps} from '#/components/Button'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Text, TextProps} from '#/components/Typography'
@@ -103,17 +104,17 @@ export function useLink({
linkRequiresWarning(href, displayText),
)
- if (requiresWarning) {
+ if (isWeb) {
e.preventDefault()
+ }
+ if (requiresWarning) {
openModal({
name: 'link-warning',
text: displayText,
href: href,
})
} else {
- e.preventDefault()
-
if (isExternal) {
openLink(href)
} else {
@@ -241,6 +242,45 @@ export function Link({
)
}
+export function BottomSheetLink({
+ children,
+ to,
+ action = 'push',
+ onPress: outerOnPress,
+ download,
+ ...rest
+}: LinkProps) {
+ const {href, isExternal, onPress} = useLink({
+ to,
+ displayText: typeof children === 'string' ? children : '',
+ action,
+ onPress: outerOnPress,
+ })
+
+ return (
+
+ {children}
+
+ )
+}
+
export type InlineLinkProps = React.PropsWithChildren<
BaseLinkProps & TextStyleProp & Pick
> &
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
index c48d6f9bd0..809df96908 100644
--- a/src/components/Menu/index.tsx
+++ b/src/components/Menu/index.tsx
@@ -1,6 +1,5 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
-import {BlueskyBottomSheetPressable as Pressable} from '@haileyok/bluesky-bottom-sheet'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import flattenReactChildren from 'react-keyed-flatten-children'
@@ -19,6 +18,7 @@ import {
ItemTextProps,
TriggerProps,
} from '#/components/Menu/types'
+import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {Text} from '#/components/Typography'
export {
@@ -117,7 +117,7 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
} = useInteractionState()
return (
-
{children}
-
+
)
}
diff --git a/src/components/Menu/types.ts b/src/components/Menu/types.ts
index e5d1d16a8d..2f7aea5de5 100644
--- a/src/components/Menu/types.ts
+++ b/src/components/Menu/types.ts
@@ -4,8 +4,6 @@ import {
GestureResponderEvent,
PressableProps,
} from 'react-native'
-import {PressableEvent} from 'react-native-gesture-handler/lib/typescript/components/Pressable/PressableProps'
-import {BueskyBottomSheetPressableProps} from '@haileyok/bluesky-bottom-sheet'
import {TextStyleProp, ViewStyleProp} from '#/alf'
import * as Dialog from '#/components/Dialog'
@@ -89,10 +87,10 @@ export type TriggerChildProps =
}
export type ItemProps = React.PropsWithChildren<
- Omit &
+ Omit &
ViewStyleProp & {
label: string
- onPress: (e: PressableEvent | GestureResponderEvent) => void
+ onPress: (e: GestureResponderEvent) => void
}
>
diff --git a/src/components/NormalizedRNGHPressable.tsx b/src/components/NormalizedRNGHPressable.tsx
new file mode 100644
index 0000000000..351005619d
--- /dev/null
+++ b/src/components/NormalizedRNGHPressable.tsx
@@ -0,0 +1,128 @@
+import React from 'react'
+import {
+ GestureResponderEvent,
+ MeasureOnSuccessCallback,
+ NativeMouseEvent,
+ NativeSyntheticEvent,
+ PressableProps,
+} from 'react-native'
+import {Pressable as BSPressable} from 'react-native-gesture-handler'
+import {PressableEvent} from 'react-native-gesture-handler/lib/typescript/components/Pressable/PressableProps'
+
+function pressableEventToGestureResponderEvent(
+ event: PressableEvent,
+ target: NormalizedRNGHPressable,
+): GestureResponderEvent {
+ return {
+ nativeEvent: {
+ ...event.nativeEvent,
+ touches: [],
+ changedTouches: [],
+ identifier: event.nativeEvent.identifier.toString(),
+ target: event.nativeEvent.target.toString(),
+ },
+ // @ts-expect-error
+ target: target,
+ // @ts-expect-error
+ currentTarget: target,
+ preventDefault() {},
+ stopPropagation() {},
+ cancelable: false,
+ defaultPrevented: false,
+ eventPhase: 0,
+ isTrusted: false,
+ bubbles: false,
+ timeStamp: event.nativeEvent.timestamp,
+ isDefaultPrevented(): boolean {
+ return false
+ },
+ isPropagationStopped(): boolean {
+ return false
+ },
+ persist() {},
+ type: 'press',
+ }
+}
+
+function pressableEventToMouseEvent(
+ event: PressableEvent,
+ target: NormalizedRNGHPressable,
+): MouseEvent & NativeSyntheticEvent {
+ return {
+ ...event.nativeEvent,
+ // @ts-expect-error
+ target: target,
+ // @ts-expect-error
+ currentTarget: target,
+ preventDefault() {},
+ stopPropagation() {},
+ cancelable: false,
+ defaultPrevented: false,
+ eventPhase: 0,
+ isTrusted: false,
+ bubbles: false,
+ timeStamp: event.nativeEvent.timestamp,
+ }
+}
+
+export class NormalizedRNGHPressable extends React.Component {
+ static displayName = 'Pressable'
+
+ measure = (_: MeasureOnSuccessCallback) => {}
+
+ measureLayout = (_: number) => {}
+
+ measureInWindow = (
+ _: (x: number, y: number, width: number, height: number) => void,
+ ) => {}
+
+ setNativeProps = (_: PressableProps) => {}
+
+ focus = () => {}
+
+ blur = () => {}
+
+ onPress = (event: PressableEvent) => {
+ if (!this.props.onPress) return
+ this.props.onPress(pressableEventToGestureResponderEvent(event, this))
+ }
+
+ onLongPress = (event: PressableEvent) => {
+ if (!this.props.onLongPress) return
+ this.props.onLongPress(pressableEventToGestureResponderEvent(event, this))
+ }
+
+ onPressIn = (event: PressableEvent) => {
+ if (!this.props.onPressIn) return
+ this.props.onPressIn(pressableEventToGestureResponderEvent(event, this))
+ }
+
+ onPressOut = (event: PressableEvent) => {
+ if (!this.props.onPressOut) return
+ this.props.onPressOut(pressableEventToGestureResponderEvent(event, this))
+ }
+
+ onHoverIn = (event: PressableEvent) => {
+ if (!this.props.onHoverIn) return
+ this.props.onHoverIn(pressableEventToMouseEvent(event, this))
+ }
+
+ onHoverOut = (event: PressableEvent) => {
+ if (!this.props.onHoverOut) return
+ this.props.onHoverOut(pressableEventToMouseEvent(event, this))
+ }
+
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/src/components/NormalizedRNGHPressable.web.tsx b/src/components/NormalizedRNGHPressable.web.tsx
new file mode 100644
index 0000000000..86f5730495
--- /dev/null
+++ b/src/components/NormalizedRNGHPressable.web.tsx
@@ -0,0 +1,3 @@
+import {Pressable as NormalizedRNGHPressable} from 'react-native'
+
+export {NormalizedRNGHPressable}
diff --git a/src/components/ReportDialog/SelectLabelerView.tsx b/src/components/ReportDialog/SelectLabelerView.tsx
index f7a8139ea5..e62dc9910b 100644
--- a/src/components/ReportDialog/SelectLabelerView.tsx
+++ b/src/components/ReportDialog/SelectLabelerView.tsx
@@ -4,10 +4,10 @@ import {AppBskyLabelerDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
import {getLabelingServiceTitle} from '#/lib/moderation'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
-import {Button, useButtonContext} from '#/components/Button'
+import {BottomSheetButton} from '#/components/BottomSheetButton'
+import {useButtonContext} from '#/components/Button'
import {Divider} from '#/components/Divider'
import * as LabelingServiceCard from '#/components/LabelingServiceCard'
import {Text} from '#/components/Typography'
@@ -39,12 +39,12 @@ export function SelectLabelerView({
{props.labelers.map(labeler => {
return (
-
+
)
})}
diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx
index 169c07d732..7e27cacf0c 100644
--- a/src/components/ReportDialog/SelectReportOptionView.tsx
+++ b/src/components/ReportDialog/SelectReportOptionView.tsx
@@ -5,7 +5,7 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {ReportOption, useReportOptions} from '#/lib/moderation/useReportOptions'
-import {Link} from '#/components/Link'
+import {BottomSheetLink} from '#/components/Link'
import {DMCA_LINK} from '#/components/ReportDialog/const'
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
@@ -129,7 +129,7 @@ export function SelectReportOptionView({
]}>
Need to report a copyright violation?
- View details
-
+
)}
diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx
index 389c722c87..22eb406a00 100644
--- a/src/components/moderation/LabelsOnMeDialog.tsx
+++ b/src/components/moderation/LabelsOnMeDialog.tsx
@@ -13,7 +13,9 @@ import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {BottomSheetButton} from '#/components/BottomSheetButton'
+import {BottomSheetInlineLinkText} from '#/components/BottomSheetLink'
+import {ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
@@ -139,7 +141,7 @@ function Label({
{!isSelfLabel && (
-
+
)}
@@ -156,23 +158,25 @@ function Label({
-
- {isSelfLabel ? (
+ {isSelfLabel ? (
+
This label was applied by you.
- ) : (
-
- Source:{' '}
- control.close()}>
- {sourceName}
-
-
- )}
-
+
+ ) : (
+
+
+ Source: {' '}
+
+ control.close()}>
+ {sourceName}
+
+
+ )}
)
@@ -275,7 +279,7 @@ function AppealForm({
? [a.flex_row, a.justify_between]
: [{flexDirection: 'column-reverse'}, a.gap_sm]
}>
-
-
+
>
)