Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

Add iOS 26 fluid zoom transition to alt text dialog (#9970)

Co-authored-by: Claude <noreply@anthropic.com>

authored by samuel.fm

Claude and committed by
GitHub
1782a651 7fd218c9

+33 -4
+2
modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetModule.kt
··· 48 48 Prop("preventExpansion") { view: BottomSheetView, prop: Boolean -> 49 49 view.preventExpansion = prop 50 50 } 51 + 52 + Prop("sourceViewTag") { _: BottomSheetView, _: Int? -> } 51 53 } 52 54 } 53 55 }
+4
modules/bottom-sheet/ios/BottomSheetModule.swift
··· 42 42 Prop("preventExpansion") { (view: SheetView, prop: Bool) in 43 43 view.preventExpansion = prop 44 44 } 45 + 46 + Prop("sourceViewTag") { (view: SheetView, prop: Int?) in 47 + view.sourceViewTag = prop 48 + } 45 49 } 46 50 } 47 51 }
+10
modules/bottom-sheet/ios/SheetView.swift
··· 26 26 var preventDismiss = false 27 27 var preventExpansion = false 28 28 var cornerRadius: CGFloat? 29 + var sourceViewTag: Int? 29 30 var minHeight = 0.0 30 31 var maxHeight: CGFloat! { 31 32 didSet { ··· 134 135 self.selectedDetentIdentifier = sheet.selectedDetentIdentifier 135 136 } 136 137 sheetVc.view.addSubview(innerView) 138 + 139 + if #available(iOS 26.0, *), 140 + let tag = self.sourceViewTag, 141 + let bridge = self.appContext?.reactBridge, 142 + let sourceView = bridge.uiManager.view(forReactTag: NSNumber(value: tag)) { 143 + sheetVc.preferredTransition = .zoom { _ in 144 + return sourceView 145 + } 146 + } 137 147 138 148 self.sheetVc = sheetVc 139 149 self.isOpening = true
+2 -2
modules/bottom-sheet/src/BottomSheet.types.ts
··· 1 - import React from 'react' 2 - import {ColorValue, NativeSyntheticEvent} from 'react-native' 1 + import {type ColorValue, type NativeSyntheticEvent} from 'react-native' 3 2 4 3 export type BottomSheetState = 'closed' | 'closing' | 'open' | 'opening' 5 4 ··· 25 24 backgroundColor?: ColorValue 26 25 containerBackgroundColor?: ColorValue 27 26 disableDrag?: boolean 27 + sourceViewTag?: number 28 28 29 29 minHeight?: number 30 30 maxHeight?: number
+12 -1
src/view/com/composer/photos/Gallery.tsx
··· 1 - import React from 'react' 1 + import React, {useState} from 'react' 2 2 import { 3 + findNodeHandle, 3 4 type ImageStyle, 4 5 Keyboard, 5 6 type LayoutChangeEvent, ··· 144 145 145 146 const altTextControl = Dialog.useDialogControl() 146 147 const editControl = Dialog.useDialogControl() 148 + const [altBtnViewTag, setAltBtnViewTag] = useState<number>() 149 + 150 + const altBtnRef = (node: View | null) => { 151 + if (node) { 152 + const tag = findNodeHandle(node) 153 + if (tag != null) setAltBtnViewTag(tag) 154 + } 155 + } 147 156 148 157 const onImageEdit = () => { 149 158 if (IS_NATIVE) { ··· 162 171 163 172 return ( 164 173 <View 174 + ref={altBtnRef} 165 175 style={imageStyle as ViewStyle} 166 176 // Fixes ALT and icons appearing with half opacity when the post is inactive 167 177 renderToHardwareTextureAndroid> ··· 240 250 control={altTextControl} 241 251 image={image} 242 252 onChange={onChange} 253 + sourceViewTag={altBtnViewTag} 243 254 /> 244 255 245 256 <EditImageDialog
+3 -1
src/view/com/composer/photos/ImageAltTextDialog.tsx
··· 23 23 control: Dialog.DialogOuterProps['control'] 24 24 image: ComposerImage 25 25 onChange: (next: ComposerImage) => void 26 + sourceViewTag?: number 26 27 } 27 28 28 29 export const ImageAltTextDialog = ({ 29 30 control, 30 31 image, 31 32 onChange, 33 + sourceViewTag, 32 34 }: Props): React.ReactNode => { 33 35 const {height: minHeight} = useWindowDimensions() 34 36 const [altText, setAltText] = useState(image.alt) ··· 42 44 alt: enforceLen(altText, MAX_ALT_TEXT, true), 43 45 }) 44 46 }} 45 - nativeOptions={{minHeight}}> 47 + nativeOptions={{minHeight, sourceViewTag}}> 46 48 <Dialog.Handle /> 47 49 <ImageAltTextInner 48 50 control={control}