Reduce lightbox button size on native (#10500)

This commit is contained in:
Samuel Newman
2026-05-25 14:34:35 +03:00
committed by GitHub
parent ebed23657f
commit 709589fa36
6 changed files with 51 additions and 65 deletions
+2 -2
View File
@@ -19,8 +19,8 @@ export function Lightbox() {
<ImageView <ImageView
lightbox={activeLightbox} lightbox={activeLightbox}
onRequestClose={onClose} onRequestClose={onClose}
onPressSave={saveImageToAlbum} onPressSave={uri => void saveImageToAlbum(uri)}
onPressShare={uri => shareImageModal({uri})} onPressShare={uri => void shareImageModal({uri})}
/> />
) )
} }
+19 -23
View File
@@ -1,9 +1,7 @@
import {useCallback, useEffect, useRef, useState} from 'react' import {useCallback, useEffect, useRef, useState} from 'react'
import {Pressable, StyleSheet, View} from 'react-native' import {Pressable, StyleSheet, View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {FocusGuards, FocusScope} from 'radix-ui/internal' import {FocusGuards, FocusScope} from 'radix-ui/internal'
import {RemoveScrollBar} from 'react-remove-scroll-bar' import {RemoveScrollBar} from 'react-remove-scroll-bar'
@@ -67,12 +65,12 @@ function LightboxContainer({
children: React.ReactNode children: React.ReactNode
handleBackgroundPress: () => void handleBackgroundPress: () => void
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
FocusGuards.useFocusGuards() FocusGuards.useFocusGuards()
return ( return (
<Pressable <Pressable
accessibilityHint={undefined} accessibilityHint={undefined}
accessibilityLabel={_(msg`Close image viewer`)} accessibilityLabel={l`Close image viewer`}
onPress={handleBackgroundPress} onPress={handleBackgroundPress}
style={[a.fixed, a.inset_0, a.z_10]}> style={[a.fixed, a.inset_0, a.z_10]}>
<Backdrop /> <Backdrop />
@@ -81,7 +79,7 @@ function LightboxContainer({
<div <div
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label={_(msg`Image viewer`)} aria-label={l`Image viewer`}
style={{position: 'absolute', inset: 0}}> style={{position: 'absolute', inset: 0}}>
{children} {children}
</div> </div>
@@ -100,7 +98,7 @@ function LightboxGallery({
onClose: () => void onClose: () => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {t: l} = useLingui()
const {reduceMotionEnabled} = useA11y() const {reduceMotionEnabled} = useA11y()
const [index, setIndex] = useState(initialIndex) const [index, setIndex] = useState(initialIndex)
const [hasAnyLoaded, setAnyHasLoaded] = useState(false) const [hasAnyLoaded, setAnyHasLoaded] = useState(false)
@@ -196,7 +194,7 @@ function LightboxGallery({
]} ]}
hoverStyle={styles.blurredBackdropHover} hoverStyle={styles.blurredBackdropHover}
color="secondary" color="secondary"
label={_(msg`Previous image`)} label={l`Previous image`}
shape="round" shape="round"
size={gtPhone ? 'large' : 'small'}> size={gtPhone ? 'large' : 'small'}>
<ChevronLeftIcon <ChevronLeftIcon
@@ -217,7 +215,7 @@ function LightboxGallery({
]} ]}
hoverStyle={styles.blurredBackdropHover} hoverStyle={styles.blurredBackdropHover}
color="secondary" color="secondary"
label={_(msg`Next image`)} label={l`Next image`}
shape="round" shape="round"
size={gtPhone ? 'large' : 'small'}> size={gtPhone ? 'large' : 'small'}>
<ChevronRightIcon <ChevronRightIcon
@@ -236,10 +234,8 @@ function LightboxGallery({
delayedFadeInAnim, delayedFadeInAnim,
]}> ]}>
<Pressable <Pressable
accessibilityLabel={_(msg`Expand alt text`)} accessibilityLabel={l`Expand alt text`}
accessibilityHint={_( accessibilityHint={l`If alt text is long, toggles alt text expanded state`}
msg`If alt text is long, toggles alt text expanded state`,
)}
onPress={() => { onPress={() => {
setAltExpanded(!isAltExpanded) setAltExpanded(!isAltExpanded)
}}> }}>
@@ -254,11 +250,11 @@ function LightboxGallery({
) : null} ) : null}
{imgs.length > 1 && ( {imgs.length > 1 && (
<div aria-live="polite" aria-atomic="true" style={a.sr_only}> <div aria-live="polite" aria-atomic="true" style={a.sr_only}>
<Text>{_(msg`Image ${index + 1} of ${imgs.length}`)}</Text> <Text>{l`Image ${index + 1} of ${imgs.length}`}</Text>
</div> </div>
)} )}
<Menu.Root> <Menu.Root>
<Menu.Trigger label={_(msg`Image options`)}> <Menu.Trigger label={l`Image options`}>
{({props}) => ( {({props}) => (
<View style={[a.absolute, styles.menuBtn, delayedFadeInAnim]}> <View style={[a.absolute, styles.menuBtn, delayedFadeInAnim]}>
<CircleChromeButton <CircleChromeButton
@@ -266,7 +262,7 @@ function LightboxGallery({
accessible={false} accessible={false}
icon={EllipsisIcon} icon={EllipsisIcon}
iconStyle={{transform: [{rotate: '90deg'}]}} iconStyle={{transform: [{rotate: '90deg'}]}}
label={_(msg`Image options`)} label={l`Image options`}
/> />
</View> </View>
)} )}
@@ -274,7 +270,7 @@ function LightboxGallery({
<Menu.Outer> <Menu.Outer>
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Share image`)} label={l`Share image`}
onPress={async () => { onPress={async () => {
const url = img.uri const url = img.uri
if ( if (
@@ -293,9 +289,9 @@ function LightboxGallery({
) { ) {
try { try {
await navigator.clipboard.writeText(url) await navigator.clipboard.writeText(url)
Toast.show(_(msg`Link copied to clipboard`)) Toast.show(l`Link copied to clipboard`)
} catch { } catch {
Toast.show(_(msg`Failed to copy link`), {type: 'error'}) Toast.show(l`Failed to copy link`, {type: 'error'})
} }
} }
}}> }}>
@@ -305,14 +301,14 @@ function LightboxGallery({
<Menu.ItemIcon icon={ShareIcon} position="right" /> <Menu.ItemIcon icon={ShareIcon} position="right" />
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
label={_(msg`Download image`)} label={l`Download image`}
onPress={() => { onPress={() => {
saveImageToMediaLibrary({uri: img.uri}).then( saveImageToMediaLibrary({uri: img.uri}).then(
() => { () => {
Toast.show(_(msg`Image saved`)) Toast.show(l`Image saved`)
}, },
() => { () => {
Toast.show(_(msg`Failed to save image`), {type: 'error'}) Toast.show(l`Failed to save image`, {type: 'error'})
}, },
) )
}}> }}>
@@ -327,7 +323,7 @@ function LightboxGallery({
<View style={[a.absolute, styles.closeBtn, delayedFadeInAnim]}> <View style={[a.absolute, styles.closeBtn, delayedFadeInAnim]}>
<CircleChromeButton <CircleChromeButton
icon={XIcon} icon={XIcon}
label={_(msg`Close image viewer`)} label={l`Close image viewer`}
onPress={onClose} onPress={onClose}
/> />
</View> </View>
@@ -8,7 +8,7 @@ import {
} from 'react-native' } from 'react-native'
import {BlurView} from 'expo-blur' import {BlurView} from 'expo-blur'
import {HITSLOP_10} from '#/lib/constants' import {HITSLOP_20} from '#/lib/constants'
import {type Props as IconProps} from '#/components/icons/common' import {type Props as IconProps} from '#/components/icons/common'
type Props = { type Props = {
@@ -27,9 +27,9 @@ type Props = {
| 'accessibilityHint' | 'accessibilityHint'
> >
const SIZE = 44 const SIZE = 32
const RADIUS = 24 const RADIUS = SIZE / 2
const ICON = 24 const ICON = 18
export function CircleChromeButton({ export function CircleChromeButton({
icon: Icon, icon: Icon,
@@ -45,7 +45,7 @@ export function CircleChromeButton({
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={label} accessibilityLabel={label}
accessibilityHint="" accessibilityHint=""
hitSlop={HITSLOP_10} hitSlop={HITSLOP_20}
onPress={onPress} onPress={onPress}
testID={testID} testID={testID}
style={({pressed}) => [styles.root, pressed && styles.pressed]}> style={({pressed}) => [styles.root, pressed && styles.pressed]}>
+14 -17
View File
@@ -7,10 +7,9 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {atoms as a} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
type Props = { type Props = {
@@ -20,7 +19,8 @@ type Props = {
} }
export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) { export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const isMomentumScrolling = useRef(false) const isMomentumScrolling = useRef(false)
@@ -28,8 +28,14 @@ export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) {
return ( return (
<View <View
style={[styles.root, {paddingBottom: insets.bottom + 8}]} style={[
pointerEvents="box-none"> a.absolute,
a.left_0,
a.right_0,
a.bottom_0,
a.pointer_events_box_none,
{paddingBottom: insets.bottom + 8},
]}>
<View style={[a.mx_md, styles.altWrap]}> <View style={[a.mx_md, styles.altWrap]}>
<ScrollView <ScrollView
scrollEnabled={isAltExpanded} scrollEnabled={isAltExpanded}
@@ -42,7 +48,7 @@ export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) {
contentContainerStyle={[a.px_md, a.py_sm]}> contentContainerStyle={[a.px_md, a.py_sm]}>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Expand alt text`)} accessibilityLabel={l`Expand alt text`}
accessibilityHint="" accessibilityHint=""
onPress={() => { onPress={() => {
if (isMomentumScrolling.current) return if (isMomentumScrolling.current) return
@@ -55,7 +61,7 @@ export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) {
<Text <Text
emoji emoji
selectable selectable
style={[a.text_sm, styles.altText]} style={[a.text_sm, {color: t.palette.white}]}
numberOfLines={isAltExpanded ? undefined : 3}> numberOfLines={isAltExpanded ? undefined : 3}>
{altText} {altText}
</Text> </Text>
@@ -67,18 +73,9 @@ export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
},
altWrap: { altWrap: {
backgroundColor: 'rgba(0, 0, 0, 0.45)', backgroundColor: 'rgba(0, 0, 0, 0.45)',
borderRadius: 12, borderRadius: 12,
overflow: 'hidden', overflow: 'hidden',
}, },
altText: {
color: '#fff',
},
}) })
+10 -17
View File
@@ -1,7 +1,6 @@
import {StyleSheet, View} from 'react-native' import {View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {TimesLarge_Stroke2_Corner0_Rounded as CloseIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as CloseIcon} from '#/components/icons/Times'
@@ -24,36 +23,30 @@ export function Header({
imageCount, imageCount,
activeIndex, activeIndex,
}: Props) { }: Props) {
const {_} = useLingui() const {t: l} = useLingui()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
return ( return (
<View <View
style={[ style={[
styles.root, a.absolute,
a.top_0,
a.left_0,
a.right_0,
a.flex_row, a.flex_row,
a.justify_between, a.justify_between,
a.align_center, a.align_center,
a.px_md, a.px_md,
a.pointer_events_box_none,
{paddingTop: insets.top + 8}, {paddingTop: insets.top + 8},
]} ]}>
pointerEvents="box-none">
<ImageMenu onPressShare={onPressShare} onPressSave={onPressSave} /> <ImageMenu onPressShare={onPressShare} onPressSave={onPressSave} />
<PagerDots count={imageCount} activeIndex={activeIndex} /> <PagerDots count={imageCount} activeIndex={activeIndex} />
<CircleChromeButton <CircleChromeButton
icon={CloseIcon} icon={CloseIcon}
label={_(msg`Close image`)} label={l`Close image`}
onPress={onRequestClose} onPress={onRequestClose}
/> />
</View> </View>
) )
} }
const styles = StyleSheet.create({
root: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
})
+1 -1
View File
@@ -84,7 +84,7 @@ export function ImageMenu({onPressShare, onPressSave}: Props) {
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={l`Close menu`} accessibilityLabel={l`Close menu`}
accessibilityHint="" accessibilityHint=""
style={StyleSheet.absoluteFill} style={[a.absolute, a.inset_0]}
onPress={close} onPress={close}
/> />
{anchor && ( {anchor && (