diff options
Diffstat (limited to 'src/view/com/util')
-rw-r--r-- | src/view/com/util/ErrorBoundary.tsx | 13 | ||||
-rw-r--r-- | src/view/com/util/Link.tsx | 169 | ||||
-rw-r--r-- | src/view/com/util/LoadLatestBtn.web.tsx | 13 | ||||
-rw-r--r-- | src/view/com/util/PostEmbeds/QuoteEmbed.tsx | 1 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 95 | ||||
-rw-r--r-- | src/view/com/util/PostMuted.tsx | 2 | ||||
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 78 | ||||
-rw-r--r-- | src/view/com/util/UserBanner.tsx | 28 | ||||
-rw-r--r-- | src/view/com/util/UserInfoText.tsx | 26 | ||||
-rw-r--r-- | src/view/com/util/ViewHeader.tsx | 92 | ||||
-rw-r--r-- | src/view/com/util/Views.web.tsx | 7 | ||||
-rw-r--r-- | src/view/com/util/forms/DropdownButton.tsx | 10 | ||||
-rw-r--r-- | src/view/com/util/forms/RadioButton.tsx | 10 | ||||
-rw-r--r-- | src/view/com/util/forms/ToggleButton.tsx | 12 |
14 files changed, 335 insertions, 221 deletions
diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx index 017265f48..c7374e195 100644 --- a/src/view/com/util/ErrorBoundary.tsx +++ b/src/view/com/util/ErrorBoundary.tsx @@ -1,5 +1,6 @@ import React, {Component, ErrorInfo, ReactNode} from 'react' import {ErrorScreen} from './error/ErrorScreen' +import {CenteredView} from './Views' interface Props { children?: ReactNode @@ -27,11 +28,13 @@ export class ErrorBoundary extends Component<Props, State> { public render() { if (this.state.hasError) { return ( - <ErrorScreen - title="Oh no!" - message="There was an unexpected issue in the application. Please let us know if this happened to you!" - details={this.state.error.toString()} - /> + <CenteredView> + <ErrorScreen + title="Oh no!" + message="There was an unexpected issue in the application. Please let us know if this happened to you!" + details={this.state.error.toString()} + /> + </CenteredView> ) } diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index bdc447937..cee4d4136 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -2,6 +2,8 @@ import React from 'react' import {observer} from 'mobx-react-lite' import { Linking, + GestureResponderEvent, + Platform, StyleProp, TouchableWithoutFeedback, TouchableOpacity, @@ -9,10 +11,22 @@ import { View, ViewStyle, } from 'react-native' +import { + useLinkProps, + useNavigation, + StackActions, +} from '@react-navigation/native' import {Text} from './text/Text' import {TypographyVariant} from 'lib/ThemeContext' +import {NavigationProp} from 'lib/routes/types' +import {router} from '../../../routes' import {useStores, RootStoreModel} from 'state/index' import {convertBskyAppUrlIfNeeded} from 'lib/strings/url-helpers' +import {isDesktopWeb} from 'platform/detection' + +type Event = + | React.MouseEvent<HTMLAnchorElement, MouseEvent> + | GestureResponderEvent export const Link = observer(function Link({ style, @@ -20,30 +34,33 @@ export const Link = observer(function Link({ title, children, noFeedback, + asAnchor, }: { style?: StyleProp<ViewStyle> href?: string title?: string children?: React.ReactNode noFeedback?: boolean + asAnchor?: boolean }) { const store = useStores() - const onPress = () => { - if (href) { - handleLink(store, href, false) - } - } - const onLongPress = () => { - if (href) { - handleLink(store, href, true) - } - } + const navigation = useNavigation<NavigationProp>() + + const onPress = React.useCallback( + (e?: Event) => { + if (typeof href === 'string') { + return onPressInner(store, navigation, href, e) + } + }, + [store, navigation, href], + ) + if (noFeedback) { return ( <TouchableWithoutFeedback onPress={onPress} - onLongPress={onLongPress} - delayPressIn={50}> + // @ts-ignore web only -prf + href={asAnchor ? href : undefined}> <View style={style}> {children ? children : <Text>{title || 'link'}</Text>} </View> @@ -52,10 +69,10 @@ export const Link = observer(function Link({ } return ( <TouchableOpacity + style={style} onPress={onPress} - onLongPress={onLongPress} - delayPressIn={50} - style={style}> + // @ts-ignore web only -prf + href={asAnchor ? href : undefined}> {children ? children : <Text>{title || 'link'}</Text>} </TouchableOpacity> ) @@ -66,35 +83,123 @@ export const TextLink = observer(function TextLink({ style, href, text, + numberOfLines, + lineHeight, }: { type?: TypographyVariant style?: StyleProp<TextStyle> href: string - text: string + text: string | JSX.Element + numberOfLines?: number + lineHeight?: number }) { + const {...props} = useLinkProps({to: href}) const store = useStores() - const onPress = () => { - handleLink(store, href, false) - } - const onLongPress = () => { - handleLink(store, href, true) + const navigation = useNavigation<NavigationProp>() + + props.onPress = React.useCallback( + (e?: Event) => { + return onPressInner(store, navigation, href, e) + }, + [store, navigation, href], + ) + + return ( + <Text + type={type} + style={style} + numberOfLines={numberOfLines} + lineHeight={lineHeight} + {...props}> + {text} + </Text> + ) +}) + +/** + * Only acts as a link on desktop web + */ +export const DesktopWebTextLink = observer(function DesktopWebTextLink({ + type = 'md', + style, + href, + text, + numberOfLines, + lineHeight, +}: { + type?: TypographyVariant + style?: StyleProp<TextStyle> + href: string + text: string | JSX.Element + numberOfLines?: number + lineHeight?: number +}) { + if (isDesktopWeb) { + return ( + <TextLink + type={type} + style={style} + href={href} + text={text} + numberOfLines={numberOfLines} + lineHeight={lineHeight} + /> + ) } return ( - <Text type={type} style={style} onPress={onPress} onLongPress={onLongPress}> + <Text + type={type} + style={style} + numberOfLines={numberOfLines} + lineHeight={lineHeight}> {text} </Text> ) }) -function handleLink(store: RootStoreModel, href: string, longPress: boolean) { - href = convertBskyAppUrlIfNeeded(href) - if (href.startsWith('http')) { - Linking.openURL(href) - } else if (longPress) { - store.shell.closeModal() // close any active modals - store.nav.newTab(href) - } else { - store.shell.closeModal() // close any active modals - store.nav.navigate(href) +// NOTE +// we can't use the onPress given by useLinkProps because it will +// match most paths to the HomeTab routes while we actually want to +// preserve the tab the app is currently in +// +// we also have some additional behaviors - closing the current modal, +// converting bsky urls, and opening http/s links in the system browser +// +// this method copies from the onPress implementation but adds our +// needed customizations +// -prf +function onPressInner( + store: RootStoreModel, + navigation: NavigationProp, + href: string, + e?: Event, +) { + let shouldHandle = false + + if (Platform.OS !== 'web' || !e) { + shouldHandle = e ? !e.defaultPrevented : true + } else if ( + !e.defaultPrevented && // onPress prevented default + // @ts-ignore Web only -prf + !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys + // @ts-ignore Web only -prf + (e.button == null || e.button === 0) && // ignore everything but left clicks + // @ts-ignore Web only -prf + [undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc. + ) { + e.preventDefault() + shouldHandle = true + } + + if (shouldHandle) { + href = convertBskyAppUrlIfNeeded(href) + if (href.startsWith('http')) { + Linking.openURL(href) + } else { + store.shell.closeModal() // close any active modals + + // @ts-ignore we're not able to type check on this one -prf + navigation.dispatch(StackActions.push(...router.matchPath(href))) + } } } diff --git a/src/view/com/util/LoadLatestBtn.web.tsx b/src/view/com/util/LoadLatestBtn.web.tsx index 182c1ba5d..ba33f92a7 100644 --- a/src/view/com/util/LoadLatestBtn.web.tsx +++ b/src/view/com/util/LoadLatestBtn.web.tsx @@ -2,6 +2,7 @@ import React from 'react' import {StyleSheet, TouchableOpacity} from 'react-native' import {Text} from './text/Text' import {usePalette} from 'lib/hooks/usePalette' +import {UpIcon} from 'lib/icons' const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} @@ -9,10 +10,11 @@ export const LoadLatestBtn = ({onPress}: {onPress: () => void}) => { const pal = usePalette('default') return ( <TouchableOpacity - style={[pal.view, styles.loadLatest]} + style={[pal.view, pal.borderDark, styles.loadLatest]} onPress={onPress} hitSlop={HITSLOP}> <Text type="md-bold" style={pal.text}> + <UpIcon size={16} strokeWidth={1} style={[pal.text, styles.icon]} /> Load new posts </Text> </TouchableOpacity> @@ -29,8 +31,15 @@ const styles = StyleSheet.create({ shadowOpacity: 0.2, shadowOffset: {width: 0, height: 2}, shadowRadius: 4, - paddingHorizontal: 24, + paddingLeft: 20, + paddingRight: 24, paddingVertical: 10, borderRadius: 30, + borderWidth: 1, + }, + icon: { + position: 'relative', + top: 2, + marginRight: 5, }, }) diff --git a/src/view/com/util/PostEmbeds/QuoteEmbed.tsx b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx index 76b71a53d..f98a66b76 100644 --- a/src/view/com/util/PostEmbeds/QuoteEmbed.tsx +++ b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx @@ -25,6 +25,7 @@ const QuoteEmbed = ({quote}: {quote: ComposerOptsQuote}) => { authorAvatar={quote.author.avatar} authorHandle={quote.author.handle} authorDisplayName={quote.author.displayName} + postHref={itemHref} timestamp={quote.indexedAt} /> <Text type="post-text" style={pal.text} numberOfLines={6}> diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index cde5a3e92..0bb402100 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,6 +1,7 @@ import React from 'react' import {StyleSheet, View} from 'react-native' import {Text} from './text/Text' +import {DesktopWebTextLink} from './Link' import {ago} from 'lib/strings/time' import {usePalette} from 'lib/hooks/usePalette' import {useStores} from 'state/index' @@ -12,6 +13,7 @@ interface PostMetaOpts { authorAvatar?: string authorHandle: string authorDisplayName: string | undefined + postHref: string timestamp: string did?: string declarationCid?: string @@ -20,8 +22,8 @@ interface PostMetaOpts { export const PostMeta = observer(function (opts: PostMetaOpts) { const pal = usePalette('default') - let displayName = opts.authorDisplayName || opts.authorHandle - let handle = opts.authorHandle + const displayName = opts.authorDisplayName || opts.authorHandle + const handle = opts.authorHandle const store = useStores() const isMe = opts.did === store.me.did const isFollowing = @@ -41,31 +43,35 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { ) { // two-liner with follow button return ( - <View style={[styles.metaTwoLine]}> + <View style={styles.metaTwoLine}> <View> - <Text - type="lg-bold" - style={[pal.text]} - numberOfLines={1} - lineHeight={1.2}> - {displayName}{' '} - <Text + <View style={styles.metaTwoLineTop}> + <DesktopWebTextLink + type="lg-bold" + style={pal.text} + numberOfLines={1} + lineHeight={1.2} + text={displayName} + href={`/profile/${opts.authorHandle}`} + /> + <Text type="md" style={pal.textLight} lineHeight={1.2}> + · + </Text> + <DesktopWebTextLink type="md" style={[styles.metaItem, pal.textLight]} - lineHeight={1.2}> - · {ago(opts.timestamp)} - </Text> - </Text> - <Text + lineHeight={1.2} + text={ago(opts.timestamp)} + href={opts.postHref} + /> + </View> + <DesktopWebTextLink type="md" style={[styles.metaItem, pal.textLight]} - lineHeight={1.2}> - {handle ? ( - <Text type="md" style={[pal.textLight]}> - @{handle} - </Text> - ) : undefined} - </Text> + lineHeight={1.2} + text={`@${handle}`} + href={`/profile/${opts.authorHandle}`} + /> </View> <View> @@ -84,31 +90,36 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { <View style={styles.meta}> {typeof opts.authorAvatar !== 'undefined' && ( <View style={[styles.metaItem, styles.avatar]}> - <UserAvatar - avatar={opts.authorAvatar} - handle={opts.authorHandle} - displayName={opts.authorDisplayName} - size={16} - /> + <UserAvatar avatar={opts.authorAvatar} size={16} /> </View> )} <View style={[styles.metaItem, styles.maxWidth]}> - <Text + <DesktopWebTextLink type="lg-bold" - style={[pal.text]} + style={pal.text} numberOfLines={1} - lineHeight={1.2}> - {displayName} - {handle ? ( - <Text type="md" style={[pal.textLight]}> - {handle} - </Text> - ) : undefined} - </Text> + lineHeight={1.2} + text={ + <> + {displayName} + <Text type="md" style={[pal.textLight]}> + {handle} + </Text> + </> + } + href={`/profile/${opts.authorHandle}`} + /> </View> - <Text type="md" style={[styles.metaItem, pal.textLight]} lineHeight={1.2}> - · {ago(opts.timestamp)} + <Text type="md" style={pal.textLight} lineHeight={1.2}> + · </Text> + <DesktopWebTextLink + type="md" + style={[styles.metaItem, pal.textLight]} + lineHeight={1.2} + text={ago(opts.timestamp)} + href={opts.postHref} + /> </View> ) }) @@ -125,6 +136,10 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', paddingBottom: 2, }, + metaTwoLineTop: { + flexDirection: 'row', + alignItems: 'baseline', + }, metaItem: { paddingRight: 5, }, diff --git a/src/view/com/util/PostMuted.tsx b/src/view/com/util/PostMuted.tsx index d8573bd56..539a71ecf 100644 --- a/src/view/com/util/PostMuted.tsx +++ b/src/view/com/util/PostMuted.tsx @@ -7,7 +7,7 @@ import {Text} from './text/Text' export function PostMutedWrapper({ isMuted, children, -}: React.PropsWithChildren<{isMuted: boolean}>) { +}: React.PropsWithChildren<{isMuted?: boolean}>) { const pal = usePalette('default') const [override, setOverride] = React.useState(false) if (!isMuted || override) { diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index d0d2c273b..2e0632521 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -1,6 +1,6 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg' +import Svg, {Circle, Path} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {IconProp} from '@fortawesome/fontawesome-svg-core' import {HighPriorityImage} from 'view/com/util/images/Image' @@ -11,52 +11,48 @@ import { PickedMedia, } from '../../../lib/media/picker' import { - requestPhotoAccessIfNeeded, - requestCameraAccessIfNeeded, -} from 'lib/permissions' + usePhotoLibraryPermission, + useCameraPermission, +} from 'lib/hooks/usePermissions' import {useStores} from 'state/index' -import {colors, gradients} from 'lib/styles' +import {colors} from 'lib/styles' import {DropdownButton} from './forms/DropdownButton' import {usePalette} from 'lib/hooks/usePalette' import {isWeb} from 'platform/detection' +function DefaultAvatar({size}: {size: number}) { + return ( + <Svg + width={size} + height={size} + viewBox="0 0 24 24" + fill="none" + stroke="none"> + <Circle cx="12" cy="12" r="12" fill="#0070ff" /> + <Circle cx="12" cy="9.5" r="3.5" fill="#fff" /> + <Path + strokeLinecap="round" + strokeLinejoin="round" + fill="#fff" + d="M 12.058 22.784 C 9.422 22.784 7.007 21.836 5.137 20.262 C 5.667 17.988 8.534 16.25 11.99 16.25 C 15.494 16.25 18.391 18.036 18.864 20.357 C 17.01 21.874 14.64 22.784 12.058 22.784 Z" + /> + </Svg> + ) +} + export function UserAvatar({ size, - handle, avatar, - displayName, onSelectNewAvatar, }: { size: number - handle: string - displayName: string | undefined avatar?: string | null onSelectNewAvatar?: (img: PickedMedia | null) => void }) { const store = useStores() const pal = usePalette('default') - const initials = getInitials(displayName || handle) - - const renderSvg = (svgSize: number, svgInitials: string) => ( - <Svg width={svgSize} height={svgSize} viewBox="0 0 100 100"> - <Defs> - <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1"> - <Stop offset="0" stopColor={gradients.blue.start} stopOpacity="1" /> - <Stop offset="1" stopColor={gradients.blue.end} stopOpacity="1" /> - </LinearGradient> - </Defs> - <Circle cx="50" cy="50" r="50" fill="url(#grad)" /> - <Text - fill="white" - fontSize="50" - fontWeight="bold" - x="50" - y="67" - textAnchor="middle"> - {svgInitials} - </Text> - </Svg> - ) + const {requestCameraAccessIfNeeded} = useCameraPermission() + const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const dropdownItems = [ !isWeb && { @@ -124,7 +120,7 @@ export function UserAvatar({ source={{uri: avatar}} /> ) : ( - renderSvg(size, initials) + <DefaultAvatar size={size} /> )} <View style={[styles.editButtonContainer, pal.btn]}> <FontAwesomeIcon @@ -141,26 +137,10 @@ export function UserAvatar({ source={{uri: avatar}} /> ) : ( - renderSvg(size, initials) + <DefaultAvatar size={size} /> ) } -function getInitials(str: string): string { - const tokens = str - .toLowerCase() - .replace(/[^a-z]/g, '') - .split(' ') - .filter(Boolean) - .map(v => v.trim()) - if (tokens.length >= 2 && tokens[0][0] && tokens[0][1]) { - return tokens[0][0].toUpperCase() + tokens[1][0].toUpperCase() - } - if (tokens.length === 1 && tokens[0][0]) { - return tokens[0][0].toUpperCase() - } - return 'X' -} - const styles = StyleSheet.create({ editButtonContainer: { position: 'absolute', diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 16e05311b..d89de9158 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,10 +1,10 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg' +import Svg, {Rect} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {IconProp} from '@fortawesome/fontawesome-svg-core' import Image from 'view/com/util/images/Image' -import {colors, gradients} from 'lib/styles' +import {colors} from 'lib/styles' import { openCamera, openCropper, @@ -13,9 +13,9 @@ import { } from '../../../lib/media/picker' import {useStores} from 'state/index' import { - requestPhotoAccessIfNeeded, - requestCameraAccessIfNeeded, -} from 'lib/permissions' + usePhotoLibraryPermission, + useCameraPermission, +} from 'lib/hooks/usePermissions' import {DropdownButton} from './forms/DropdownButton' import {usePalette} from 'lib/hooks/usePalette' import {isWeb} from 'platform/detection' @@ -29,6 +29,9 @@ export function UserBanner({ }) { const store = useStores() const pal = usePalette('default') + const {requestCameraAccessIfNeeded} = useCameraPermission() + const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() + const dropdownItems = [ !isWeb && { label: 'Camera', @@ -80,19 +83,8 @@ export function UserBanner({ ] const renderSvg = () => ( - <Svg width="100%" height="150" viewBox="50 0 200 100"> - <Defs> - <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1"> - <Stop - offset="0" - stopColor={gradients.blueDark.start} - stopOpacity="1" - /> - <Stop offset="1" stopColor={gradients.blueDark.end} stopOpacity="1" /> - </LinearGradient> - </Defs> - <Rect x="0" y="0" width="400" height="100" fill="url(#grad)" /> - <Rect x="0" y="0" width="400" height="100" fill="url(#grad2)" /> + <Svg width="100%" height="150" viewBox="0 0 400 100"> + <Rect x="0" y="0" width="400" height="100" fill="#0070ff" /> </Svg> ) diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 84170b3bf..4753c9b01 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,7 +1,7 @@ import React, {useState, useEffect} from 'react' import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' import {StyleProp, StyleSheet, TextStyle} from 'react-native' -import {Link} from './Link' +import {DesktopWebTextLink} from './Link' import {Text} from './text/Text' import {LoadingPlaceholder} from './LoadingPlaceholder' import {useStores} from 'state/index' @@ -14,7 +14,6 @@ export function UserInfoText({ failed, prefix, style, - asLink, }: { type?: TypographyVariant did: string @@ -23,7 +22,6 @@ export function UserInfoText({ failed?: string prefix?: string style?: StyleProp<TextStyle> - asLink?: boolean }) { attr = attr || 'handle' failed = failed || 'user' @@ -64,9 +62,14 @@ export function UserInfoText({ ) } else if (profile) { inner = ( - <Text type={type} style={style} lineHeight={1.2} numberOfLines={1}>{`${ - prefix || '' - }${profile[attr] || profile.handle}`}</Text> + <DesktopWebTextLink + type={type} + style={style} + lineHeight={1.2} + numberOfLines={1} + href={`/profile/${profile.handle}`} + text={`${prefix || ''}${profile[attr] || profile.handle}`} + /> ) } else { inner = ( @@ -78,17 +81,6 @@ export function UserInfoText({ ) } - if (asLink) { - const title = profile?.displayName || profile?.handle || 'User' - return ( - <Link - href={`/profile/${profile?.handle ? profile.handle : did}`} - title={title}> - {inner} - </Link> - ) - } - return inner } diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index ffd1b1d63..a99282512 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -2,17 +2,19 @@ import React from 'react' import {observer} from 'mobx-react-lite' import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useNavigation} from '@react-navigation/native' import {UserAvatar} from './UserAvatar' import {Text} from './text/Text' import {useStores} from 'state/index' import {usePalette} from 'lib/hooks/usePalette' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {useAnalytics} from 'lib/analytics' -import {isDesktopWeb} from '../../../platform/detection' +import {NavigationProp} from 'lib/routes/types' +import {isDesktopWeb} from 'platform/detection' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} -export const ViewHeader = observer(function ViewHeader({ +export const ViewHeader = observer(function ({ title, canGoBack, hideOnScroll, @@ -23,50 +25,55 @@ export const ViewHeader = observer(function ViewHeader({ }) { const pal = usePalette('default') const store = useStores() + const navigation = useNavigation<NavigationProp>() const {track} = useAnalytics() - const onPressBack = () => { - store.nav.tab.goBack() - } - const onPressMenu = () => { + + const onPressBack = React.useCallback(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') + } + }, [navigation]) + + const onPressMenu = React.useCallback(() => { track('ViewHeader:MenuButtonClicked') - store.shell.setMainMenuOpen(true) - } - if (typeof canGoBack === 'undefined') { - canGoBack = store.nav.tab.canGoBack - } + store.shell.openDrawer() + }, [track, store]) + if (isDesktopWeb) { return <></> + } else { + if (typeof canGoBack === 'undefined') { + canGoBack = navigation.canGoBack() + } + + return ( + <Container hideOnScroll={hideOnScroll || false}> + <TouchableOpacity + testID="viewHeaderBackOrMenuBtn" + onPress={canGoBack ? onPressBack : onPressMenu} + hitSlop={BACK_HITSLOP} + style={canGoBack ? styles.backBtn : styles.backBtnWide}> + {canGoBack ? ( + <FontAwesomeIcon + size={18} + icon="angle-left" + style={[styles.backIcon, pal.text]} + /> + ) : ( + <UserAvatar size={30} avatar={store.me.avatar} /> + )} + </TouchableOpacity> + <View style={styles.titleContainer} pointerEvents="none"> + <Text type="title" style={[pal.text, styles.title]}> + {title} + </Text> + </View> + <View style={canGoBack ? styles.backBtn : styles.backBtnWide} /> + </Container> + ) } - return ( - <Container hideOnScroll={hideOnScroll || false}> - <TouchableOpacity - testID="viewHeaderBackOrMenuBtn" - onPress={canGoBack ? onPressBack : onPressMenu} - hitSlop={BACK_HITSLOP} - style={canGoBack ? styles.backBtn : styles.backBtnWide}> - {canGoBack ? ( - <FontAwesomeIcon - size={18} - icon="angle-left" - style={[styles.backIcon, pal.text]} - /> - ) : ( - <UserAvatar - size={30} - handle={store.me.handle} - displayName={store.me.displayName} - avatar={store.me.avatar} - /> - )} - </TouchableOpacity> - <View style={styles.titleContainer} pointerEvents="none"> - <Text type="title" style={[pal.text, styles.title]}> - {title} - </Text> - </View> - <View style={canGoBack ? styles.backBtn : styles.backBtnWide} /> - </Container> - ) }) const Container = observer( @@ -119,8 +126,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, - paddingTop: 6, - paddingBottom: 6, + paddingVertical: 6, }, headerFloating: { position: 'absolute', diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index 8b5adaa04..9a43697b5 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -23,7 +23,6 @@ import { ViewProps, } from 'react-native' import {addStyle, colors} from 'lib/styles' -import {DESKTOP_HEADER_HEIGHT} from 'lib/constants' export function CenteredView({ style, @@ -73,14 +72,14 @@ export const ScrollView = React.forwardRef(function ( const styles = StyleSheet.create({ container: { width: '100%', - maxWidth: 550, + maxWidth: 600, marginLeft: 'auto', marginRight: 'auto', }, containerScroll: { width: '100%', - height: `calc(100vh - ${DESKTOP_HEADER_HEIGHT}px)`, - maxWidth: 550, + minHeight: '100vh', + maxWidth: 600, marginLeft: 'auto', marginRight: 'auto', }, diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index ac83d1a54..d6ae800c6 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -17,7 +17,6 @@ import {Button, ButtonType} from './Button' import {colors} from 'lib/styles' import {toShareUrl} from 'lib/strings/url-helpers' import {useStores} from 'state/index' -import {TABS_ENABLED} from 'lib/build-flags' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' @@ -138,15 +137,6 @@ export function PostDropdownBtn({ const store = useStores() const dropdownItems: DropdownItem[] = [ - TABS_ENABLED - ? { - icon: ['far', 'clone'], - label: 'Open in new tab', - onPress() { - store.nav.newTab(itemHref) - }, - } - : undefined, { icon: 'language', label: 'Translate...', diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx index 57a875cd3..d6b2bb119 100644 --- a/src/view/com/util/forms/RadioButton.tsx +++ b/src/view/com/util/forms/RadioButton.tsx @@ -41,6 +41,9 @@ export function RadioButton({ 'secondary-light': { borderColor: theme.palette.secondary.border, }, + default: { + borderColor: theme.palette.default.border, + }, 'default-light': { borderColor: theme.palette.default.border, }, @@ -69,6 +72,9 @@ export function RadioButton({ 'secondary-light': { backgroundColor: theme.palette.secondary.background, }, + default: { + backgroundColor: theme.palette.primary.background, + }, 'default-light': { backgroundColor: theme.palette.primary.background, }, @@ -103,6 +109,10 @@ export function RadioButton({ color: theme.palette.secondary.textInverted, fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, }, + default: { + color: theme.palette.default.text, + fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + }, 'default-light': { color: theme.palette.default.text, fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, diff --git a/src/view/com/util/forms/ToggleButton.tsx b/src/view/com/util/forms/ToggleButton.tsx index 005d1165e..a6e0ba3fe 100644 --- a/src/view/com/util/forms/ToggleButton.tsx +++ b/src/view/com/util/forms/ToggleButton.tsx @@ -42,6 +42,9 @@ export function ToggleButton({ 'secondary-light': { borderColor: theme.palette.secondary.border, }, + default: { + borderColor: theme.palette.default.border, + }, 'default-light': { borderColor: theme.palette.default.border, }, @@ -77,6 +80,11 @@ export function ToggleButton({ backgroundColor: theme.palette.secondary.background, opacity: isSelected ? 1 : 0.5, }, + default: { + backgroundColor: isSelected + ? theme.palette.primary.background + : colors.gray3, + }, 'default-light': { backgroundColor: isSelected ? theme.palette.primary.background @@ -113,6 +121,10 @@ export function ToggleButton({ color: theme.palette.secondary.textInverted, fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, }, + default: { + color: theme.palette.default.text, + fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + }, 'default-light': { color: theme.palette.default.text, fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, |