-
Notifications
You must be signed in to change notification settings - Fork 3.8k
#2 - Update advanced filters button to open filters popover #91806
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
Open
bernhardoj
wants to merge
22
commits into
Expensify:main
Choose a base branch
from
bernhardoj:feat/84877-update-filters-chip-to-open-filters-popup
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
05a6eef
remove unnecessary code from useAdvancedSearchFilters
bernhardoj b627c69
fix filter list doesn't change when type changes by letting the consu…
bernhardoj 4f1419b
improve the selector so it returns string so the comparison becomes m…
bernhardoj 8176a7b
Revert "Merge pull request #91721 from Expensify/jsenyitko-revert-fil…
bernhardoj 389fde4
remove unused prop
bernhardoj ec78f4d
migrate the advanced filters from modal to navigation screens on smal…
bernhardoj 488913a
fix filter list is not updated after changing type
bernhardoj dc76d0b
remove unused component
bernhardoj 86e965f
add press on enter
bernhardoj 99b8cc5
sort list
bernhardoj 63f2565
don't show modal when not focused
bernhardoj 07f60b0
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj a908324
use existing selector
bernhardoj 5747db2
show not found when filter key is invalid
bernhardoj cfef3f1
fix test
bernhardoj 68b8524
add missing import
bernhardoj b028ce9
reorganize and lint
bernhardoj 1f6e4a0
fix spelling
bernhardoj 6ed6fc3
fix type
bernhardoj e59fb90
fix autofocus
bernhardoj 122ac92
lint
bernhardoj a47d510
lint
bernhardoj 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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,11 @@ | ||
| import type SafeTriangleProps from './types'; | ||
|
|
||
| /** | ||
| * A component that provides a "safe triangle" wrapper. | ||
| * On native platforms, hover interactions are not applicable, so this is a no-op wrapper. | ||
| */ | ||
| function SafeTriangle({children}: SafeTriangleProps) { | ||
| return children; | ||
| } | ||
|
|
||
| export default SafeTriangle; |
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,194 @@ | ||
| import React, {useEffect, useRef, useState} from 'react'; | ||
| import {View} from 'react-native'; | ||
| import {Polygon, Svg} from 'react-native-svg'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {isMobile} from '@libs/Browser'; | ||
| import htmlDivElementRef from '@src/types/utils/htmlDivElementRef'; | ||
| import type SafeTriangleProps from './types'; | ||
|
|
||
| type Point = [number, number]; | ||
|
|
||
| type SafeTriangleOverlayProps = { | ||
| submenuRef: React.RefObject<View | null>; | ||
| containerRef: React.RefObject<View | null>; | ||
| }; | ||
|
|
||
| type Rect = { | ||
| top: number; | ||
| left: number; | ||
| width: number; | ||
| height: number; | ||
| }; | ||
|
|
||
| /** Time in ms before the safe triangle is cleared after cursor stops moving toward submenu */ | ||
| const SAFE_TRIANGLE_CLEAR_DELAY_MS = 50; | ||
|
|
||
| const OFFSET = 2; | ||
|
|
||
| function isPointInPolygon(point: Point, polygon: Point[]) { | ||
| const [x, y] = point; | ||
| let isInside = false; | ||
| const length = polygon.length; | ||
| for (let i = 0, j = length - 1; i < length; j = i++) { | ||
| const [xi, yi] = polygon.at(i) ?? [0, 0]; | ||
| const [xj, yj] = polygon.at(j) ?? [0, 0]; | ||
| const intersect = yi >= y !== yj >= y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi; | ||
| if (intersect) { | ||
| isInside = !isInside; | ||
| } | ||
| } | ||
| return isInside; | ||
| } | ||
|
|
||
| function SafeTriangleOverlay({submenuRef, containerRef}: SafeTriangleOverlayProps) { | ||
| const styles = useThemeStyles(); | ||
|
|
||
| const [points, setPoints] = useState<string | null>(null); | ||
| const [svgRect, setSvgRect] = useState<Rect | null>(null); | ||
|
|
||
| const apexRef = useRef<Point | null>(null); | ||
| const lastCursorPosition = useRef<Point | null>(null); | ||
| const lastCursorTime = useRef<number>(0); | ||
| const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); | ||
|
|
||
| const getCursorSpeed = (x: number, y: number): number | null => { | ||
| const currentTime = performance.now(); | ||
| const elapsedTime = currentTime - lastCursorTime.current; | ||
|
|
||
| if (lastCursorPosition.current === null || elapsedTime === 0) { | ||
| lastCursorPosition.current = [x, y]; | ||
| lastCursorTime.current = currentTime; | ||
| return null; | ||
| } | ||
|
|
||
| const deltaX = x - lastCursorPosition.current[0]; | ||
| const deltaY = y - lastCursorPosition.current[1]; | ||
| const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); | ||
| const speed = distance / elapsedTime; // px / ms | ||
|
|
||
| lastCursorPosition.current = [x, y]; | ||
| lastCursorTime.current = currentTime; | ||
|
|
||
| return speed; | ||
| }; | ||
|
|
||
| const clearTriangle = () => { | ||
| setPoints(null); | ||
| setSvgRect(null); | ||
| apexRef.current = null; | ||
| }; | ||
|
|
||
| const onMouseMove = (event: MouseEvent) => { | ||
| clearTimeout(timeoutRef.current); | ||
|
|
||
| const {clientX, clientY} = event; | ||
| const speed = getCursorSpeed(clientX, clientY); | ||
|
|
||
| if (!submenuRef.current) { | ||
| clearTriangle(); | ||
| return; | ||
| } | ||
|
|
||
| const rect = submenuRef.current.getBoundingClientRect(); | ||
| if (!rect) { | ||
| clearTriangle(); | ||
| return; | ||
| } | ||
|
|
||
| // If speed is slow, update the apex to the current cursor position | ||
| if (speed === null || speed < 0.1) { | ||
| apexRef.current = [clientX, clientY]; | ||
| } | ||
|
|
||
| const [x, y] = apexRef.current ?? [clientX, clientY]; | ||
|
|
||
| // Create a polygon from apex to the submenu's left edge | ||
| const cursorPoint: Point = [0, y - rect.top]; | ||
| // We subtract OFFSET from x-coordinates to account for the offset in the container's left style | ||
| const topLeftSubMenuPoint: Point = [rect.left - x - OFFSET, 0]; | ||
| const bottomLeftSubMenuPoint: Point = [rect.left - x - OFFSET, rect.bottom - rect.top]; | ||
|
|
||
| const polygon = [cursorPoint, topLeftSubMenuPoint, bottomLeftSubMenuPoint]; | ||
|
|
||
| // Check if the current mouse position is within the safe triangle | ||
| // The polygon points are relative to [x + OFFSET, rect.top], so we adjust the mouse position accordingly | ||
| const isSafe = isPointInPolygon([clientX - x + OFFSET, clientY - rect.top], polygon); | ||
|
|
||
| if (isSafe) { | ||
| const pointsString = polygon.map((p) => p.join(',')).join(' '); | ||
| setPoints(pointsString); | ||
| setSvgRect({ | ||
| top: rect.top, | ||
| left: x + OFFSET, | ||
| height: rect.height, | ||
| width: rect.left - x - OFFSET, | ||
| }); | ||
| timeoutRef.current = setTimeout(clearTriangle, SAFE_TRIANGLE_CLEAR_DELAY_MS); | ||
| } else { | ||
| clearTriangle(); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| const container = htmlDivElementRef(containerRef).current; | ||
| if (!container) { | ||
| return; | ||
| } | ||
|
|
||
| container.addEventListener('mousemove', onMouseMove, true); | ||
|
|
||
| return () => { | ||
| container.removeEventListener('mousemove', onMouseMove, true); | ||
| clearTimeout(timeoutRef.current); | ||
| }; | ||
| }, [onMouseMove, containerRef]); | ||
|
|
||
| if (!points || !svgRect) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <Svg | ||
| style={[styles.pFixed, styles.cursorPointer, svgRect, {zIndex: 1000}]} | ||
|
bernhardoj marked this conversation as resolved.
|
||
| width={svgRect.width} | ||
| height={svgRect.height} | ||
| onWheel={clearTriangle} | ||
| pointerEvents="none" | ||
| > | ||
| <Polygon | ||
| points={points} | ||
| fill="transparent" | ||
| pointerEvents="auto" | ||
| /> | ||
| </Svg> | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * A component that creates a "safe triangle" area between the cursor and a submenu. | ||
| * This prevents the submenu from switching when the user moves the cursor | ||
| * diagonally towards the submenu by rendering an invisible SVG overlay. | ||
| */ | ||
| function SafeTriangle({submenuRef, children}: SafeTriangleProps) { | ||
| const styles = useThemeStyles(); | ||
| const containerRef = useRef<View>(null); | ||
|
|
||
| if (isMobile()) { | ||
| return children; | ||
| } | ||
|
|
||
| return ( | ||
| <View | ||
| ref={containerRef} | ||
| style={styles.flex1} | ||
| > | ||
| {children} | ||
| <SafeTriangleOverlay | ||
| containerRef={containerRef} | ||
| submenuRef={submenuRef} | ||
| /> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| export default SafeTriangle; | ||
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,8 @@ | ||
| import type {View} from 'react-native'; | ||
|
|
||
| type SafeTriangleProps = { | ||
| submenuRef: React.RefObject<View | null>; | ||
| children: React.ReactNode; | ||
| }; | ||
|
|
||
| export default SafeTriangleProps; |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For mobile view, any reason for not using existing legacy filter pages?
App/src/ROUTES.ts
Lines 747 to 756 in 52e1696
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we're going with navigation path, why not just reorder filter menu items in old filters page but completely refactored?