Updates to use dynamic/responsive styles on web (#1351)

* Move most responsive queries to the hook

* Fix invalid CSS value

* Fixes to tablet render of post thread

* Fix overflow issues on web

* Fix search header on tablet

* Fix QP margin in web composer

* Fix: only apply double gutter once to flatlist (close #1368)

* Fix styles on discover feeds header

* Fix double discover links in multifeed
This commit is contained in:
Paul Frazee 2023-09-05 10:42:19 -07:00 committed by GitHub
parent be8084ae10
commit 764c7cd569
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 762 additions and 461 deletions

View file

@ -8,9 +8,9 @@ import {Text} from './text/Text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useAnalytics} from 'lib/analytics/analytics'
import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb} from 'platform/detection'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
@ -35,6 +35,7 @@ export const ViewHeader = observer(function ({
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics()
const {isDesktop, isTablet} = useWebMediaQueries()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
@ -49,7 +50,7 @@ export const ViewHeader = observer(function ({
store.shell.openDrawer()
}, [track, store])
if (isDesktopWeb) {
if (isDesktop) {
if (showOnDesktop) {
return (
<DesktopWebHeader
@ -84,13 +85,13 @@ export const ViewHeader = observer(function ({
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : (
) : !isTablet ? (
<FontAwesomeIcon
size={18}
icon="bars"
style={[styles.backIcon, pal.textLight]}
/>
)}
) : null}
</TouchableOpacity>
) : null}
<View style={styles.titleContainer} pointerEvents="none">
@ -122,6 +123,7 @@ function DesktopWebHeader({
<CenteredView
style={[
styles.header,
styles.headerFixed,
styles.desktopHeader,
pal.border,
{
@ -178,6 +180,7 @@ const Container = observer(
<View
style={[
styles.header,
styles.headerFixed,
pal.view,
pal.border,
showBorder && styles.border,
@ -190,9 +193,9 @@ const Container = observer(
<Animated.View
style={[
styles.header,
styles.headerFloating,
pal.view,
pal.border,
styles.headerFloating,
transform,
showBorder && styles.border,
]}>
@ -208,6 +211,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
paddingHorizontal: 12,
paddingVertical: 6,
width: '100%',
},
headerFixed: {
maxWidth: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
headerFloating: {
position: 'absolute',

View file

@ -24,6 +24,7 @@ import {
} from 'react-native'
import {addStyle} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
interface AddedProps {
desktopFixedHeight?: boolean
@ -48,6 +49,7 @@ export const FlatList = React.forwardRef(function <ItemT>(
ref: React.Ref<RNFlatList>,
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerScroll,
@ -67,6 +69,12 @@ export const FlatList = React.forwardRef(function <ItemT>(
}
if (desktopFixedHeight) {
style = addStyle(style, styles.fixedHeight)
if (!isMobile) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.stableGutters,
)
}
}
return (
<RNFlatList
@ -126,6 +134,9 @@ const styles = StyleSheet.create({
},
fixedHeight: {
height: '100vh',
},
stableGutters: {
// @ts-ignore web only -prf
scrollbarGutter: 'stable both-edges',
},
})

View file

@ -5,7 +5,8 @@ import LinearGradient from 'react-native-linear-gradient'
import {gradients} from 'lib/styles'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {useStores} from 'state/index'
import {isMobileWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isWeb} from 'platform/detection'
export interface FABProps
extends ComponentProps<typeof TouchableWithoutFeedback> {
@ -14,6 +15,7 @@ export interface FABProps
}
export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
const {isTablet} = useWebMediaQueries()
const store = useStores()
const interp = useAnimatedValue(0)
React.useEffect(() => {
@ -24,18 +26,33 @@ export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
isInteraction: false,
}).start()
}, [interp, store.shell.minimalShellMode])
const transform = {
transform: [{translateY: Animated.multiply(interp, 60)}],
}
const transform = isTablet
? undefined
: {
transform: [{translateY: Animated.multiply(interp, 60)}],
}
const size = isTablet ? styles.sizeLarge : styles.sizeRegular
return (
<TouchableWithoutFeedback testID={testID} {...props}>
<Animated.View
style={[styles.outer, isMobileWeb && styles.mobileWebOuter, transform]}>
style={[
styles.outer,
size,
isWeb && isTablet
? {
right: 50,
bottom: 50,
}
: {
bottom: 114,
},
transform,
]}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={styles.inner}>
style={[styles.inner, size]}>
{icon}
</LinearGradient>
</Animated.View>
@ -44,22 +61,23 @@ export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
})
const styles = StyleSheet.create({
sizeRegular: {
width: 60,
height: 60,
borderRadius: 30,
},
sizeLarge: {
width: 70,
height: 70,
borderRadius: 35,
},
outer: {
position: 'absolute',
zIndex: 1,
right: 24,
bottom: 94,
width: 60,
height: 60,
borderRadius: 30,
},
mobileWebOuter: {
bottom: 114,
},
inner: {
width: 60,
height: 60,
borderRadius: 30,
justifyContent: 'center',
alignItems: 'center',
},

View file

@ -2,7 +2,7 @@ import React from 'react'
import {Pressable, ViewStyle, StyleProp, StyleSheet} from 'react-native'
import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
interface SelectableBtnProps {
testID?: string
@ -28,12 +28,16 @@ export function SelectableBtn({
const pal = usePalette('default')
const palPrimary = usePalette('inverted')
const needsWidthStyles = !style || !('width' in style || 'flex' in style)
const {isMobile} = useWebMediaQueries()
return (
<Pressable
testID={testID}
style={[
styles.btn,
needsWidthStyles && styles.btnWidth,
needsWidthStyles && {
flex: isMobile ? 1 : undefined,
width: !isMobile ? 100 : undefined,
},
left && styles.btnLeft,
right && styles.btnRight,
pal.border,
@ -58,10 +62,6 @@ const styles = StyleSheet.create({
paddingHorizontal: 10,
paddingVertical: 10,
},
btnWidth: {
flex: isDesktopWeb ? undefined : 1,
width: isDesktopWeb ? 100 : undefined,
},
btnLeft: {
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,

View file

@ -2,18 +2,18 @@ import React from 'react'
import MediaQuery from 'react-responsive'
export const Desktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={1224}>{children}</MediaQuery>
<MediaQuery minWidth={1300}>{children}</MediaQuery>
)
export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800}>{children}</MediaQuery>
)
export const Tablet = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800} maxWidth={1224}>
<MediaQuery minWidth={800} maxWidth={1300}>
{children}
</MediaQuery>
)
export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={1224}>{children}</MediaQuery>
<MediaQuery maxWidth={1300}>{children}</MediaQuery>
)
export const Mobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={800}>{children}</MediaQuery>

View file

@ -3,8 +3,8 @@ import {StyleSheet, TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile'
import {isMobileWeb} from 'platform/detection'
import {HITSLOP_20} from 'lib/constants'
export const LoadLatestBtn = ({
@ -19,7 +19,8 @@ export const LoadLatestBtn = ({
minimalShellMode?: boolean
}) => {
const pal = usePalette('default')
if (isMobileWeb) {
const {isMobile} = useWebMediaQueries()
if (isMobile) {
return (
<LoadLatestBtnMobile
onPress={onPress}

View file

@ -1,12 +1,12 @@
import React from 'react'
import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ModerationUI} from '@atproto/api'
import {Text} from '../text/Text'
import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
export function ContentHider({
testID,
@ -24,6 +24,7 @@ export function ContentHider({
}>) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false)
if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) {
@ -54,6 +55,7 @@ export function ContentHider({
accessibilityLabel=""
style={[
styles.cover,
{paddingRight: isMobile ? 22 : 18},
moderation.noOverride
? {borderWidth: 1, borderColor: pal.colors.borderDark}
: pal.viewLight,
@ -96,7 +98,6 @@ const styles = StyleSheet.create({
marginTop: 4,
paddingVertical: 14,
paddingLeft: 14,
paddingRight: isDesktopWeb ? 18 : 22,
},
showBtn: {
marginLeft: 'auto',

View file

@ -2,13 +2,13 @@ import React, {ComponentProps} from 'react'
import {StyleSheet, Pressable, View} from 'react-native'
import {ModerationUI} from '@atproto/api'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Link} from '../Link'
import {Text} from '../text/Text'
import {addStyle} from 'lib/styles'
import {describeModerationCause} from 'lib/moderation'
import {ShieldExclamation} from 'lib/icons'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
interface Props extends ComponentProps<typeof Link> {
// testID?: string
@ -27,6 +27,7 @@ export function PostHider({
}: Props) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false)
if (!moderation.blur) {
@ -55,7 +56,11 @@ export function PostHider({
accessibilityRole="button"
accessibilityHint={override ? 'Hide the content' : 'Show the content'}
accessibilityLabel=""
style={[styles.description, pal.viewLight]}>
style={[
styles.description,
{paddingRight: isMobile ? 22 : 18},
pal.viewLight,
]}>
<Pressable
onPress={() => {
store.shell.openModal({
@ -100,7 +105,6 @@ const styles = StyleSheet.create({
gap: 4,
paddingVertical: 14,
paddingLeft: 18,
paddingRight: isDesktopWeb ? 18 : 22,
marginTop: 1,
},
showBtn: {

View file

@ -13,10 +13,10 @@ import {
import {useNavigation} from '@react-navigation/native'
import {ModerationUI} from '@atproto/api'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {Text} from '../text/Text'
import {Button} from '../forms/Button'
import {isDesktopWeb} from 'platform/detection'
import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index'
@ -39,6 +39,7 @@ export function ScreenHider({
const palInverted = usePalette('inverted')
const [override, setOverride] = React.useState(false)
const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries()
if (!moderation.blur || override) {
return (
@ -85,7 +86,7 @@ export function ScreenHider({
</Text>
</TouchableWithoutFeedback>
</Text>
{!isDesktopWeb && <View style={styles.spacer} />}
{isMobile && <View style={styles.spacer} />}
<View style={styles.btnContainer}>
<Button
type="inverted"

View file

@ -3,8 +3,8 @@ import {Image} from 'expo-image'
import {Text} from '../text/Text'
import {StyleSheet, View} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {AppBskyEmbedExternal} from '@atproto/api'
import {isDesktopWeb} from 'platform/detection'
import {toNiceDomain} from 'lib/strings/url-helpers'
export const ExternalLinkEmbed = ({
@ -15,10 +15,31 @@ export const ExternalLinkEmbed = ({
imageChild?: React.ReactNode
}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<View style={styles.extContainer}>
<View
style={{
flexDirection: isMobile ? 'column' : 'row',
}}>
{link.thumb ? (
<View style={styles.extImageContainer}>
<View
style={
!isMobile
? {
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
width: 120,
aspectRatio: 1,
overflow: 'hidden',
}
: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
height: 200,
overflow: 'hidden',
}
}>
<Image
style={styles.extImage}
source={{uri: link.thumb}}
@ -27,7 +48,13 @@ export const ExternalLinkEmbed = ({
{imageChild}
</View>
) : undefined}
<View style={styles.extInner}>
<View
style={{
paddingHorizontal: isMobile ? 10 : 14,
paddingTop: 8,
paddingBottom: 10,
flex: !isMobile ? 1 : undefined,
}}>
<Text
type="sm"
numberOfLines={1}
@ -36,14 +63,14 @@ export const ExternalLinkEmbed = ({
</Text>
<Text
type="lg-bold"
numberOfLines={isDesktopWeb ? 2 : 4}
numberOfLines={isMobile ? 4 : 2}
style={[pal.text]}>
{link.title || link.uri}
</Text>
{link.description ? (
<Text
type="md"
numberOfLines={isDesktopWeb ? 2 : 4}
numberOfLines={isMobile ? 4 : 2}
style={[pal.text, styles.extDescription]}>
{link.description}
</Text>
@ -54,30 +81,6 @@ export const ExternalLinkEmbed = ({
}
const styles = StyleSheet.create({
extContainer: {
flexDirection: isDesktopWeb ? 'row' : 'column',
},
extInner: {
paddingHorizontal: isDesktopWeb ? 14 : 10,
paddingTop: 8,
paddingBottom: 10,
flex: isDesktopWeb ? 1 : undefined,
},
extImageContainer: isDesktopWeb
? {
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
width: 120,
aspectRatio: 1,
overflow: 'hidden',
}
: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
height: 200,
overflow: 'hidden',
},
extImage: {
width: '100%',
height: 200,

View file

@ -22,6 +22,7 @@ import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ImagesLightbox} from 'state/models/ui/shell'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {YoutubeEmbed} from './YoutubeEmbed'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
@ -29,7 +30,6 @@ import {MaybeQuoteEmbed} from './QuoteEmbed'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {CustomFeedEmbed} from './CustomFeedEmbed'
import {ListEmbed} from './ListEmbed'
import {isDesktopWeb} from 'platform/detection'
import {isCauseALabelOnUri} from 'lib/moderation'
type Embed =
@ -50,6 +50,7 @@ export function PostEmbeds({
}) {
const pal = usePalette('default')
const store = useStores()
const {isMobile} = useWebMediaQueries()
// quote post with media
// =
@ -111,7 +112,10 @@ export function PostEmbeds({
uri={thumb}
onPress={() => openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={styles.singleImage}>
style={[
styles.singleImage,
isMobile && styles.singleImageMobile,
]}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text style={styles.alt} accessible={false}>
@ -130,7 +134,11 @@ export function PostEmbeds({
images={embed.images}
onPress={openLightbox}
onPressIn={onPressIn}
style={embed.images.length === 1 ? styles.singleImage : undefined}
style={
embed.images.length === 1
? [styles.singleImage, isMobile && styles.singleImageMobile]
: undefined
}
/>
</View>
)
@ -169,7 +177,10 @@ const styles = StyleSheet.create({
},
singleImage: {
borderRadius: 8,
maxHeight: isDesktopWeb ? 1000 : 500,
maxHeight: 1000,
},
singleImageMobile: {
maxHeight: 500,
},
extOuter: {
borderWidth: 1,