Redo web lightbox, add animations and keyboard a11y (#9481)
* redo web lightbox, add animations * add rect-avi to shell * fix lingui msgs
This commit is contained in:
+3
-1
@@ -5,6 +5,8 @@ import {CARD_ASPECT_RATIO} from '#/lib/constants'
|
|||||||
import {native, platform, web} from '#/alf/util/platform'
|
import {native, platform, web} from '#/alf/util/platform'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
|
const EXP_CURVE = 'cubic-bezier(0.16, 1, 0.3, 1)'
|
||||||
|
|
||||||
export const atoms = {
|
export const atoms = {
|
||||||
...baseAtoms,
|
...baseAtoms,
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ export const atoms = {
|
|||||||
}),
|
}),
|
||||||
// special composite animation for dialogs
|
// special composite animation for dialogs
|
||||||
zoom_fade_in: web({
|
zoom_fade_in: web({
|
||||||
animation: 'zoomIn ease-out 0.1s, fadeIn ease-out 0.1s',
|
animation: `zoomIn ${EXP_CURVE} 0.3s, fadeIn ${EXP_CURVE} 0.3s`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -409,3 +409,7 @@ export function Handle({
|
|||||||
export function Close() {
|
export function Close() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Backdrop() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {
|
|||||||
FlatList,
|
FlatList,
|
||||||
type FlatListProps,
|
type FlatListProps,
|
||||||
type GestureResponderEvent,
|
type GestureResponderEvent,
|
||||||
|
Pressable,
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
TouchableWithoutFeedback,
|
|
||||||
View,
|
View,
|
||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
@@ -113,7 +113,7 @@ export function Outer({
|
|||||||
<Portal>
|
<Portal>
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
<RemoveScrollBar />
|
<RemoveScrollBar />
|
||||||
<TouchableWithoutFeedback
|
<Pressable
|
||||||
accessibilityHint={undefined}
|
accessibilityHint={undefined}
|
||||||
accessibilityLabel={_(msg`Close active dialog`)}
|
accessibilityLabel={_(msg`Close active dialog`)}
|
||||||
onPress={handleBackgroundPress}>
|
onPress={handleBackgroundPress}>
|
||||||
@@ -146,7 +146,7 @@ export function Outer({
|
|||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</Pressable>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
@@ -304,7 +304,7 @@ export function Handle() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function Backdrop() {
|
export function Backdrop() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {reduceMotionEnabled} = useA11y()
|
const {reduceMotionEnabled} = useA11y()
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ let ProfileHeaderShell = ({
|
|||||||
(
|
(
|
||||||
uri: string,
|
uri: string,
|
||||||
thumbRect: MeasuredDimensions | null,
|
thumbRect: MeasuredDimensions | null,
|
||||||
type: 'circle-avi' | 'image' = 'circle-avi',
|
type: 'circle-avi' | 'rect-avi' | 'image' = 'circle-avi',
|
||||||
) => {
|
) => {
|
||||||
openLightbox({
|
openLightbox({
|
||||||
images: [
|
images: [
|
||||||
@@ -87,7 +87,7 @@ let ProfileHeaderShell = ({
|
|||||||
thumbUri: uri,
|
thumbUri: uri,
|
||||||
thumbRect,
|
thumbRect,
|
||||||
dimensions:
|
dimensions:
|
||||||
type === 'circle-avi'
|
type === 'circle-avi' || type === 'rect-avi'
|
||||||
? {
|
? {
|
||||||
// It's fine if it's actually smaller but we know it's 1:1.
|
// It's fine if it's actually smaller but we know it's 1:1.
|
||||||
height: 1000,
|
height: 1000,
|
||||||
@@ -129,11 +129,12 @@ let ProfileHeaderShell = ({
|
|||||||
} else {
|
} else {
|
||||||
const modui = moderation.ui('avatar')
|
const modui = moderation.ui('avatar')
|
||||||
const avatar = profile.avatar
|
const avatar = profile.avatar
|
||||||
|
const type = profile.associated?.labeler ? 'rect-avi' : 'circle-avi'
|
||||||
if (avatar && !(modui.blur && modui.noOverride)) {
|
if (avatar && !(modui.blur && modui.noOverride)) {
|
||||||
runOnUI(() => {
|
runOnUI(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const rect = measure(aviRef)
|
const rect = measure(aviRef)
|
||||||
runOnJS(_openLightbox)(avatar, rect)
|
runOnJS(_openLightbox)(avatar, rect, type)
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
import React, {useCallback, useEffect, useState} from 'react'
|
import {useCallback, useEffect, useState} from 'react'
|
||||||
import {
|
import {Pressable, StyleSheet, View} from 'react-native'
|
||||||
Image,
|
import {Image} from 'expo-image'
|
||||||
type ImageStyle,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
TouchableWithoutFeedback,
|
|
||||||
View,
|
|
||||||
type ViewStyle,
|
|
||||||
} from 'react-native'
|
|
||||||
import {
|
|
||||||
FontAwesomeIcon,
|
|
||||||
type FontAwesomeIconStyle,
|
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {FocusGuards, FocusScope} from 'radix-ui/internal'
|
||||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useA11y} from '#/state/a11y'
|
||||||
import {colors, s} from '#/lib/styles'
|
|
||||||
import {useLightbox, useLightboxControls} from '#/state/lightbox'
|
import {useLightbox, useLightboxControls} from '#/state/lightbox'
|
||||||
import {Text} from '../util/text/Text'
|
import {
|
||||||
|
atoms as a,
|
||||||
|
flatten,
|
||||||
|
ThemeProvider,
|
||||||
|
useBreakpoints,
|
||||||
|
useTheme,
|
||||||
|
} from '#/alf'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
import {Backdrop} from '#/components/Dialog'
|
||||||
|
import {
|
||||||
|
ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeftIcon,
|
||||||
|
ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon,
|
||||||
|
} from '#/components/icons/Chevron'
|
||||||
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
import {type ImageSource} from './ImageViewing/@types'
|
import {type ImageSource} from './ImageViewing/@types'
|
||||||
import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader'
|
|
||||||
|
|
||||||
export function Lightbox() {
|
export function Lightbox() {
|
||||||
const {activeLightbox} = useLightbox()
|
const {activeLightbox} = useLightbox()
|
||||||
@@ -36,18 +38,44 @@ export function Lightbox() {
|
|||||||
const initialIndex = activeLightbox.index
|
const initialIndex = activeLightbox.index
|
||||||
const imgs = activeLightbox.images
|
const imgs = activeLightbox.images
|
||||||
return (
|
return (
|
||||||
<>
|
<ThemeProvider theme="dark">
|
||||||
<RemoveScrollBar />
|
<LightboxContainer handleBackgroundPress={closeLightbox}>
|
||||||
<LightboxInner
|
<LightboxGallery
|
||||||
imgs={imgs}
|
key={activeLightbox.id}
|
||||||
initialIndex={initialIndex}
|
imgs={imgs}
|
||||||
onClose={closeLightbox}
|
initialIndex={initialIndex}
|
||||||
/>
|
onClose={closeLightbox}
|
||||||
</>
|
/>
|
||||||
|
</LightboxContainer>
|
||||||
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LightboxInner({
|
function LightboxContainer({
|
||||||
|
children,
|
||||||
|
handleBackgroundPress,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
handleBackgroundPress: () => void
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
FocusGuards.useFocusGuards()
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
accessibilityLabel={_(msg`Close image lightbox`)}
|
||||||
|
onPress={handleBackgroundPress}
|
||||||
|
style={[a.fixed, a.inset_0, a.z_10]}>
|
||||||
|
<Backdrop />
|
||||||
|
<RemoveScrollBar />
|
||||||
|
<FocusScope.FocusScope loop trapped asChild>
|
||||||
|
<div style={{position: 'absolute', inset: 0}}>{children}</div>
|
||||||
|
</FocusScope.FocusScope>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LightboxGallery({
|
||||||
imgs,
|
imgs,
|
||||||
initialIndex = 0,
|
initialIndex = 0,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -56,10 +84,15 @@ function LightboxInner({
|
|||||||
initialIndex: number
|
initialIndex: number
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const [index, setIndex] = useState<number>(initialIndex)
|
const {reduceMotionEnabled} = useA11y()
|
||||||
|
const [index, setIndex] = useState(initialIndex)
|
||||||
|
const [hasAnyLoaded, setAnyHasLoaded] = useState(false)
|
||||||
const [isAltExpanded, setAltExpanded] = useState(false)
|
const [isAltExpanded, setAltExpanded] = useState(false)
|
||||||
|
|
||||||
|
const {gtPhone} = useBreakpoints()
|
||||||
|
|
||||||
const canGoLeft = index >= 1
|
const canGoLeft = index >= 1
|
||||||
const canGoRight = index < imgs.length - 1
|
const canGoRight = index < imgs.length - 1
|
||||||
const onPressLeft = useCallback(() => {
|
const onPressLeft = useCallback(() => {
|
||||||
@@ -92,95 +125,69 @@ function LightboxInner({
|
|||||||
return () => window.removeEventListener('keydown', onKeyDown)
|
return () => window.removeEventListener('keydown', onKeyDown)
|
||||||
}, [onKeyDown])
|
}, [onKeyDown])
|
||||||
|
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const delayedFadeInAnim = !reduceMotionEnabled && [
|
||||||
const btnStyle = React.useMemo(() => {
|
a.fade_in,
|
||||||
return isTabletOrDesktop ? styles.btnTablet : styles.btnMobile
|
{animationDelay: '0.2s', animationFillMode: 'both'},
|
||||||
}, [isTabletOrDesktop])
|
]
|
||||||
const iconSize = React.useMemo(() => {
|
|
||||||
return isTabletOrDesktop ? 32 : 24
|
|
||||||
}, [isTabletOrDesktop])
|
|
||||||
|
|
||||||
const img = imgs[index]
|
const img = imgs[index]
|
||||||
const isAvi = img.type === 'circle-avi' || img.type === 'rect-avi'
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.mask}>
|
<View style={[a.absolute, a.inset_0]}>
|
||||||
<TouchableWithoutFeedback
|
<View style={[a.flex_1, a.justify_center, a.align_center]}>
|
||||||
onPress={onClose}
|
<LightboxGalleryItem
|
||||||
accessibilityRole="button"
|
key={index}
|
||||||
accessibilityLabel={_(msg`Close image viewer`)}
|
source={img.uri}
|
||||||
accessibilityHint={_(msg`Exits image view`)}
|
alt={img.alt}
|
||||||
onAccessibilityEscape={onClose}>
|
type={img.type}
|
||||||
{isAvi ? (
|
hasAnyLoaded={hasAnyLoaded}
|
||||||
<View style={styles.aviCenterer}>
|
onLoad={() => setAnyHasLoaded(true)}
|
||||||
<img
|
/>
|
||||||
src={img.uri}
|
{canGoLeft && (
|
||||||
// @ts-ignore web-only
|
<Button
|
||||||
style={
|
onPress={onPressLeft}
|
||||||
{
|
style={[
|
||||||
...styles.avi,
|
a.absolute,
|
||||||
borderRadius:
|
styles.leftBtn,
|
||||||
img.type === 'circle-avi'
|
styles.blurredBackdrop,
|
||||||
? '50%'
|
a.transition_color,
|
||||||
: img.type === 'rect-avi'
|
delayedFadeInAnim,
|
||||||
? '10%'
|
]}
|
||||||
: 0,
|
hoverStyle={styles.blurredBackdropHover}
|
||||||
} as ImageStyle
|
color="secondary"
|
||||||
}
|
label={_(msg`Previous image`)}
|
||||||
alt={img.alt}
|
shape="round"
|
||||||
|
size={gtPhone ? 'large' : 'small'}>
|
||||||
|
<ChevronLeftIcon
|
||||||
|
size={gtPhone ? 'md' : 'sm'}
|
||||||
|
style={{color: t.palette.white}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</Button>
|
||||||
) : (
|
|
||||||
<View style={styles.imageCenterer}>
|
|
||||||
<Image
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
source={img}
|
|
||||||
style={styles.image as ImageStyle}
|
|
||||||
accessibilityLabel={img.alt}
|
|
||||||
accessibilityHint=""
|
|
||||||
/>
|
|
||||||
{canGoLeft && (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPressLeft}
|
|
||||||
style={[
|
|
||||||
styles.btn,
|
|
||||||
btnStyle,
|
|
||||||
styles.leftBtn,
|
|
||||||
styles.blurredBackground,
|
|
||||||
]}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Previous image`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="angle-left"
|
|
||||||
style={styles.icon as FontAwesomeIconStyle}
|
|
||||||
size={iconSize}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
{canGoRight && (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPressRight}
|
|
||||||
style={[
|
|
||||||
styles.btn,
|
|
||||||
btnStyle,
|
|
||||||
styles.rightBtn,
|
|
||||||
styles.blurredBackground,
|
|
||||||
]}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Next image`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="angle-right"
|
|
||||||
style={styles.icon as FontAwesomeIconStyle}
|
|
||||||
size={iconSize}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</TouchableWithoutFeedback>
|
{canGoRight && (
|
||||||
|
<Button
|
||||||
|
onPress={onPressRight}
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
styles.rightBtn,
|
||||||
|
styles.blurredBackdrop,
|
||||||
|
a.transition_color,
|
||||||
|
delayedFadeInAnim,
|
||||||
|
]}
|
||||||
|
hoverStyle={styles.blurredBackdropHover}
|
||||||
|
color="secondary"
|
||||||
|
label={_(msg`Next image`)}
|
||||||
|
shape="round"
|
||||||
|
size={gtPhone ? 'large' : 'small'}>
|
||||||
|
<ChevronRightIcon
|
||||||
|
size={gtPhone ? 'md' : 'sm'}
|
||||||
|
style={{color: t.palette.white}}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
{img.alt ? (
|
{img.alt ? (
|
||||||
<View style={styles.footer}>
|
<View style={[a.px_4xl, a.py_2xl, t.atoms.bg, delayedFadeInAnim]}>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityLabel={_(msg`Expand alt text`)}
|
accessibilityLabel={_(msg`Expand alt text`)}
|
||||||
accessibilityHint={_(
|
accessibilityHint={_(
|
||||||
@@ -190,7 +197,7 @@ function LightboxInner({
|
|||||||
setAltExpanded(!isAltExpanded)
|
setAltExpanded(!isAltExpanded)
|
||||||
}}>
|
}}>
|
||||||
<Text
|
<Text
|
||||||
style={s.white}
|
style={[a.text_md, a.leading_snug]}
|
||||||
numberOfLines={isAltExpanded ? 0 : 3}
|
numberOfLines={isAltExpanded ? 0 : 3}
|
||||||
ellipsizeMode="tail">
|
ellipsizeMode="tail">
|
||||||
{img.alt}
|
{img.alt}
|
||||||
@@ -198,38 +205,125 @@ function LightboxInner({
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<View style={styles.closeBtn}>
|
<Button
|
||||||
<ImageDefaultHeader onRequestClose={onClose} />
|
onPress={onClose}
|
||||||
</View>
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
styles.closeBtn,
|
||||||
|
styles.blurredBackdrop,
|
||||||
|
a.transition_color,
|
||||||
|
delayedFadeInAnim,
|
||||||
|
]}
|
||||||
|
hoverStyle={styles.blurredBackdropHover}
|
||||||
|
color="secondary"
|
||||||
|
label={_(msg`Close lightbox`)}
|
||||||
|
shape="round"
|
||||||
|
size={gtPhone ? 'large' : 'small'}>
|
||||||
|
<XIcon size={gtPhone ? 'md' : 'sm'} style={{color: t.palette.white}} />
|
||||||
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LightboxGalleryItem({
|
||||||
|
source,
|
||||||
|
alt,
|
||||||
|
type,
|
||||||
|
onLoad,
|
||||||
|
hasAnyLoaded,
|
||||||
|
}: {
|
||||||
|
source: string
|
||||||
|
alt: string | undefined
|
||||||
|
type: ImageSource['type']
|
||||||
|
onLoad: () => void
|
||||||
|
hasAnyLoaded: boolean
|
||||||
|
}) {
|
||||||
|
const {reduceMotionEnabled} = useA11y()
|
||||||
|
const [hasLoaded, setHasLoaded] = useState(false)
|
||||||
|
const [isFirstToLoad] = useState(!hasAnyLoaded)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We want to show a zoom/fade in animation when the lightbox first opens.
|
||||||
|
* To avoid showing it as we switch between images, we keep track in the parent
|
||||||
|
* whether any image has loaded yet. We then save what the value of this is on first
|
||||||
|
* render (as when it changes, we don't want to then *remove* then animation). when
|
||||||
|
* the image loads, if this is the first image to load, we play the animation.
|
||||||
|
*
|
||||||
|
* We also use this `hasLoaded` state to show a loading indicator. This is on a 1s
|
||||||
|
* delay and then a slow fade in to avoid flicker. -sfn
|
||||||
|
*/
|
||||||
|
const zoomInWhenReady =
|
||||||
|
!reduceMotionEnabled &&
|
||||||
|
isFirstToLoad &&
|
||||||
|
(hasAnyLoaded
|
||||||
|
? [a.zoom_fade_in, {animationDuration: '0.5s'}]
|
||||||
|
: {opacity: 0})
|
||||||
|
|
||||||
|
const handleLoad = () => {
|
||||||
|
setHasLoaded(true)
|
||||||
|
onLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = null
|
||||||
|
switch (type) {
|
||||||
|
case 'circle-avi':
|
||||||
|
case 'rect-avi':
|
||||||
|
image = (
|
||||||
|
<img
|
||||||
|
src={source}
|
||||||
|
style={flatten([
|
||||||
|
styles.avi,
|
||||||
|
{
|
||||||
|
borderRadius:
|
||||||
|
type === 'circle-avi' ? '50%' : type === 'rect-avi' ? '10%' : 0,
|
||||||
|
},
|
||||||
|
zoomInWhenReady,
|
||||||
|
])}
|
||||||
|
alt={alt}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'image':
|
||||||
|
image = (
|
||||||
|
<Image
|
||||||
|
source={{uri: source}}
|
||||||
|
alt={alt}
|
||||||
|
style={[a.w_full, a.h_full, zoomInWhenReady]}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
contentFit="contain"
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{image}
|
||||||
|
{!hasLoaded && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
a.fade_in,
|
||||||
|
{
|
||||||
|
opacity: 0,
|
||||||
|
animationDuration: '500ms',
|
||||||
|
animationDelay: '1s',
|
||||||
|
animationFillMode: 'both',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Loader size="xl" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
mask: {
|
|
||||||
// @ts-ignore
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: '#000c',
|
|
||||||
},
|
|
||||||
imageCenterer: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
resizeMode: 'contain',
|
|
||||||
},
|
|
||||||
aviCenterer: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
avi: {
|
avi: {
|
||||||
// @ts-ignore web-only
|
// @ts-ignore web-only
|
||||||
maxWidth: `calc(min(400px, 100vw))`,
|
maxWidth: `calc(min(400px, 100vw))`,
|
||||||
@@ -238,49 +332,26 @@ const styles = StyleSheet.create({
|
|||||||
padding: 16,
|
padding: 16,
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
},
|
},
|
||||||
icon: {
|
|
||||||
color: colors.white,
|
|
||||||
},
|
|
||||||
closeBtn: {
|
closeBtn: {
|
||||||
position: 'absolute',
|
top: 20,
|
||||||
top: 10,
|
|
||||||
right: 10,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
position: 'absolute',
|
|
||||||
backgroundColor: '#00000077',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
btnTablet: {
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 25,
|
|
||||||
left: 30,
|
|
||||||
right: 30,
|
|
||||||
},
|
|
||||||
btnMobile: {
|
|
||||||
width: 44,
|
|
||||||
height: 44,
|
|
||||||
borderRadius: 22,
|
|
||||||
left: 20,
|
|
||||||
right: 20,
|
right: 20,
|
||||||
},
|
},
|
||||||
leftBtn: {
|
leftBtn: {
|
||||||
|
left: 20,
|
||||||
right: 'auto',
|
right: 'auto',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
},
|
},
|
||||||
rightBtn: {
|
rightBtn: {
|
||||||
|
right: 20,
|
||||||
left: 'auto',
|
left: 'auto',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
},
|
},
|
||||||
footer: {
|
blurredBackdrop: {
|
||||||
paddingHorizontal: 32,
|
backgroundColor: '#00000077',
|
||||||
paddingVertical: 24,
|
// @ts-expect-error web only -sfn
|
||||||
backgroundColor: colors.black,
|
|
||||||
},
|
|
||||||
blurredBackground: {
|
|
||||||
backdropFilter: 'blur(10px)',
|
backdropFilter: 'blur(10px)',
|
||||||
WebkitBackdropFilter: 'blur(10px)',
|
},
|
||||||
} as ViewStyle,
|
blurredBackdropHover: {
|
||||||
|
backgroundColor: '#00000088',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user