-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Hover-to-view for receipt thumbnails #65184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mjasikowski
merged 23 commits into
Expensify:main
from
software-mansion-labs:borys3kk-feature-zoom-receipt-on-hover
Aug 4, 2025
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
6aed543
add custom component for displaying enlraged receipt image
borys3kk 59341be
create styles for ReceiptPreview
borys3kk 04eb1b8
Merge branch 'main' into borys3kk-feature-zoom-receipt-on-hover
borys3kk 5b3e4a6
fix review comments (remove unnecesary styles, define constants for t…
borys3kk df9a008
update styles and display of image in preview
borys3kk 358eb3b
readd ReceiptAudit
borys3kk 6eee0e7
change border, add ereceipts, add fade, add verified/issues found
borys3kk f5fee55
Update gradient for ReceiptHover component
Kicu 3fa5131
Update styling for ReceiptHover component
Kicu 875bce4
remove receiphoveraudit, update styles
borys3kk 2b71822
fix ereceipt vertical lines
borys3kk e03e334
change maxHeight, remove hover in narrow layout
borys3kk 2347038
remove props from native.ts, update variables, extract props for Rece…
borys3kk ea7aaf5
Add small style fixes To Receipt Preview
Kicu b757e38
fix distance ereceipts/refactor
borys3kk fa9930e
fix tests
borys3kk 20a98b9
update proptypes for future use in receipt previews/ fix display of d…
borys3kk d72c556
remove console log
borys3kk 5d0961f
fix test
borys3kk 18f70c0
Merge branch 'main' of github.com:Expensify/App into borys3kk-feature…
borys3kk 8e9d8c2
refactor styles, receiptPreview component, fix displaying of distance…
borys3kk d5d4a59
move to const
borys3kk 818cfe7
move to const
borys3kk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
src/components/TransactionItemRow/ReceiptPreview/index.native.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| function ReceiptPreview() { | ||
| return null; | ||
| } | ||
|
|
||
| export default ReceiptPreview; |
146 changes: 146 additions & 0 deletions
146
src/components/TransactionItemRow/ReceiptPreview/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import React, {useCallback, useEffect, useRef, useState} from 'react'; | ||
| import ReactDOM from 'react-dom'; | ||
| import type {LayoutChangeEvent} from 'react-native'; | ||
| import {View} from 'react-native'; | ||
| import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; | ||
| import DistanceEReceipt from '@components/DistanceEReceipt'; | ||
| import EReceipt from '@components/EReceipt'; | ||
| import BaseImage from '@components/Image/BaseImage'; | ||
| import type {ImageOnLoadEvent} from '@components/Image/types'; | ||
| import useDebouncedState from '@hooks/useDebouncedState'; | ||
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import useWindowDimensions from '@hooks/useWindowDimensions'; | ||
| import {isDistanceRequest} from '@libs/TransactionUtils'; | ||
| import variables from '@styles/variables'; | ||
| import CONST from '@src/CONST'; | ||
| import type {Transaction} from '@src/types/onyx'; | ||
|
|
||
| const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; | ||
|
|
||
| type ReceiptPreviewProps = { | ||
| /** Path to the image to be opened in the preview */ | ||
| source: string; | ||
|
|
||
| /** Whether the preview should be shown (e.g. if we are hovered over certain ReceiptCell) */ | ||
| hovered: boolean; | ||
|
|
||
| /** Is preview for an e-receipt */ | ||
| isEReceipt: boolean; | ||
|
|
||
| /** Transaction object related to the preview */ | ||
| transactionItem: Transaction; | ||
| }; | ||
|
|
||
| function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: ReceiptPreviewProps) { | ||
| const isDistanceEReceipt = isDistanceRequest(transactionItem); | ||
| const styles = useThemeStyles(); | ||
| const [eReceiptScaleFactor, setEReceiptScaleFactor] = useState(0); | ||
| const [imageAspectRatio, setImageAspectRatio] = useState<string | number | undefined>(undefined); | ||
| const [distanceEReceiptAspectRatio, setDistanceEReceiptAspectRatio] = useState<string | number | undefined>(undefined); | ||
| const [shouldShow, debounceShouldShow, setShouldShow] = useDebouncedState(false, CONST.TIMING.SHOW_HOVER_PREVIEW_DELAY); | ||
| const {shouldUseNarrowLayout} = useResponsiveLayout(); | ||
| const hasMeasured = useRef(false); | ||
| const {windowHeight} = useWindowDimensions(); | ||
|
|
||
| const handleDistanceEReceiptLayout = (e: LayoutChangeEvent) => { | ||
| if (hasMeasured.current) { | ||
| return; | ||
| } | ||
| hasMeasured.current = true; | ||
|
|
||
| const {height, width} = e.nativeEvent.layout; | ||
| if (height === 0) { | ||
| // on the initial layout, measured height is 0, so we want to set everything on the second one | ||
| hasMeasured.current = false; | ||
| return; | ||
| } | ||
| if (height * eReceiptScaleFactor > windowHeight - CONST.RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN) { | ||
| setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - CONST.RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN)); | ||
| return; | ||
| } | ||
| setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / height); | ||
| setEReceiptScaleFactor(width / variables.eReceiptBGHWidth); | ||
| }; | ||
|
|
||
| const updateImageAspectRatio = useCallback( | ||
| (width: number, height: number) => { | ||
| if (!source) { | ||
| return; | ||
| } | ||
|
|
||
| setImageAspectRatio(height ? width / height : 'auto'); | ||
| }, | ||
| [source], | ||
| ); | ||
|
|
||
| const handleLoad = useCallback( | ||
| (event: ImageOnLoadEvent) => { | ||
| const {width, height} = event.nativeEvent; | ||
|
|
||
| updateImageAspectRatio(width, height); | ||
| }, | ||
| [updateImageAspectRatio], | ||
| ); | ||
|
|
||
| const handleEReceiptLayout = (e: LayoutChangeEvent) => { | ||
| const {width} = e.nativeEvent.layout; | ||
| setEReceiptScaleFactor(width / variables.eReceiptBGHWidth); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| setShouldShow(hovered); | ||
| }, [hovered, setShouldShow]); | ||
|
|
||
| if (shouldUseNarrowLayout || !debounceShouldShow || !shouldShow || (!source && !isEReceipt && !isDistanceEReceipt)) { | ||
| return null; | ||
| } | ||
|
|
||
| const shouldShowImage = source && !(isEReceipt || isDistanceEReceipt); | ||
| const shouldShowDistanceEReceipt = isDistanceEReceipt && !isEReceipt; | ||
|
|
||
| return ReactDOM.createPortal( | ||
| <Animated.View | ||
| entering={FadeIn.duration(CONST.TIMING.SHOW_HOVER_PREVIEW_ANIMATION_DURATION)} | ||
| exiting={FadeOut.duration(CONST.TIMING.SHOW_HOVER_PREVIEW_ANIMATION_DURATION)} | ||
| style={[styles.receiptPreview, styles.flexColumn, styles.alignItemsCenter, styles.justifyContentStart]} | ||
| > | ||
| {shouldShowImage ? ( | ||
| <View style={[styles.w100]}> | ||
| <BaseImage | ||
| source={{uri: source}} | ||
| style={[styles.w100, {aspectRatio: imageAspectRatio}]} | ||
| onLoad={handleLoad} | ||
| /> | ||
| </View> | ||
| ) : ( | ||
| <View style={styles.receiptPreviewEReceiptsContainer}> | ||
| {shouldShowDistanceEReceipt ? ( | ||
| <View | ||
| onLayout={handleDistanceEReceiptLayout} | ||
| style={[{transformOrigin: 'center', scale: eReceiptScaleFactor, aspectRatio: distanceEReceiptAspectRatio}]} | ||
| > | ||
| <DistanceEReceipt | ||
| transaction={transactionItem} | ||
| hoverPreview | ||
| /> | ||
| </View> | ||
| ) : ( | ||
| <View | ||
| onLayout={handleEReceiptLayout} | ||
| style={[styles.receiptPreviewEReceipt, {aspectRatio: eReceiptAspectRatio, scale: eReceiptScaleFactor}]} | ||
| > | ||
| <EReceipt | ||
| transactionID={transactionItem.transactionID} | ||
| transactionItem={transactionItem} | ||
| /> | ||
| </View> | ||
| )} | ||
| </View> | ||
| )} | ||
| </Animated.View>, | ||
| document.body, | ||
| ); | ||
| } | ||
|
|
||
| export default ReceiptPreview; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.