From 1782a65174cbd22f01936f529f954e7a491b3fac Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 5 Mar 2026 02:32:08 +0000 Subject: [PATCH] Add iOS 26 fluid zoom transition to alt text dialog (#9970) Co-authored-by: Claude --- .../expo/modules/bottomsheet/BottomSheetModule.kt | 2 ++ modules/bottom-sheet/ios/BottomSheetModule.swift | 4 ++++ modules/bottom-sheet/ios/SheetView.swift | 10 ++++++++++ modules/bottom-sheet/src/BottomSheet.types.ts | 4 ++-- src/view/com/composer/photos/Gallery.tsx | 13 ++++++++++++- src/view/com/composer/photos/ImageAltTextDialog.tsx | 4 +++- 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetModule.kt b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetModule.kt index 057e6ed2e2..6e3630d570 100644 --- a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetModule.kt +++ b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetModule.kt @@ -48,6 +48,8 @@ class BottomSheetModule : Module() { Prop("preventExpansion") { view: BottomSheetView, prop: Boolean -> view.preventExpansion = prop } + + Prop("sourceViewTag") { _: BottomSheetView, _: Int? -> } } } } diff --git a/modules/bottom-sheet/ios/BottomSheetModule.swift b/modules/bottom-sheet/ios/BottomSheetModule.swift index 579608e75b..2269fbd910 100644 --- a/modules/bottom-sheet/ios/BottomSheetModule.swift +++ b/modules/bottom-sheet/ios/BottomSheetModule.swift @@ -42,6 +42,10 @@ public class BottomSheetModule: Module { Prop("preventExpansion") { (view: SheetView, prop: Bool) in view.preventExpansion = prop } + + Prop("sourceViewTag") { (view: SheetView, prop: Int?) in + view.sourceViewTag = prop + } } } } diff --git a/modules/bottom-sheet/ios/SheetView.swift b/modules/bottom-sheet/ios/SheetView.swift index 68cf38f631..360c7a9101 100644 --- a/modules/bottom-sheet/ios/SheetView.swift +++ b/modules/bottom-sheet/ios/SheetView.swift @@ -26,6 +26,7 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { var preventDismiss = false var preventExpansion = false var cornerRadius: CGFloat? + var sourceViewTag: Int? var minHeight = 0.0 var maxHeight: CGFloat! { didSet { @@ -135,6 +136,15 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { } sheetVc.view.addSubview(innerView) + if #available(iOS 26.0, *), + let tag = self.sourceViewTag, + let bridge = self.appContext?.reactBridge, + let sourceView = bridge.uiManager.view(forReactTag: NSNumber(value: tag)) { + sheetVc.preferredTransition = .zoom { _ in + return sourceView + } + } + self.sheetVc = sheetVc self.isOpening = true diff --git a/modules/bottom-sheet/src/BottomSheet.types.ts b/modules/bottom-sheet/src/BottomSheet.types.ts index 150932d423..ab43404536 100644 --- a/modules/bottom-sheet/src/BottomSheet.types.ts +++ b/modules/bottom-sheet/src/BottomSheet.types.ts @@ -1,5 +1,4 @@ -import React from 'react' -import {ColorValue, NativeSyntheticEvent} from 'react-native' +import {type ColorValue, type NativeSyntheticEvent} from 'react-native' export type BottomSheetState = 'closed' | 'closing' | 'open' | 'opening' @@ -25,6 +24,7 @@ export interface BottomSheetViewProps { backgroundColor?: ColorValue containerBackgroundColor?: ColorValue disableDrag?: boolean + sourceViewTag?: number minHeight?: number maxHeight?: number diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index 044264fa03..45ce414064 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -1,5 +1,6 @@ -import React from 'react' +import React, {useState} from 'react' import { + findNodeHandle, type ImageStyle, Keyboard, type LayoutChangeEvent, @@ -144,6 +145,14 @@ const GalleryItem = ({ const altTextControl = Dialog.useDialogControl() const editControl = Dialog.useDialogControl() + const [altBtnViewTag, setAltBtnViewTag] = useState() + + const altBtnRef = (node: View | null) => { + if (node) { + const tag = findNodeHandle(node) + if (tag != null) setAltBtnViewTag(tag) + } + } const onImageEdit = () => { if (IS_NATIVE) { @@ -162,6 +171,7 @@ const GalleryItem = ({ return ( @@ -240,6 +250,7 @@ const GalleryItem = ({ control={altTextControl} image={image} onChange={onChange} + sourceViewTag={altBtnViewTag} /> void + sourceViewTag?: number } export const ImageAltTextDialog = ({ control, image, onChange, + sourceViewTag, }: Props): React.ReactNode => { const {height: minHeight} = useWindowDimensions() const [altText, setAltText] = useState(image.alt) @@ -42,7 +44,7 @@ export const ImageAltTextDialog = ({ alt: enforceLen(altText, MAX_ALT_TEXT, true), }) }} - nativeOptions={{minHeight}}> + nativeOptions={{minHeight, sourceViewTag}}>