Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
e871e30
update advanced filters button to open filters popover
bernhardoj May 8, 2026
08de1f9
update text
bernhardoj May 8, 2026
c9f0ff0
update icon
bernhardoj May 8, 2026
4f046a1
fix typo
bernhardoj May 8, 2026
6494bbb
compressed svgs
bernhardoj May 8, 2026
39408c1
lint
bernhardoj May 8, 2026
c41f25c
use a const
bernhardoj May 8, 2026
d2c8363
fix 0 is applied when amount is empty
bernhardoj May 8, 2026
ac1f36f
fix type
bernhardoj May 8, 2026
005706c
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 8, 2026
60da789
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 9, 2026
cd2962e
fix missing popover width for medium screen
bernhardoj May 9, 2026
578ce2a
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 11, 2026
6c04b5c
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 11, 2026
9f1cf0d
hide scroll bar
bernhardoj May 11, 2026
57420fd
keep hovered style when expanded
bernhardoj May 11, 2026
c01415f
make the popover right aligned
bernhardoj May 11, 2026
de7eb78
apply the padding to the list content container style
bernhardoj May 11, 2026
6910745
change max height to 500 or window height, whichever is smaller
bernhardoj May 11, 2026
99f43cf
lint
bernhardoj May 11, 2026
39cfc19
update clear button style to mimic ghost variant
bernhardoj May 11, 2026
f5f0c76
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 11, 2026
8d68975
update max height
bernhardoj May 11, 2026
248b146
render the clear button on the hook consumer component
bernhardoj May 11, 2026
2ba302f
add comment
bernhardoj May 11, 2026
f34f94c
lint
bernhardoj May 11, 2026
d931fd0
add comment
bernhardoj May 12, 2026
507924a
add padding as scroll view offset
bernhardoj May 12, 2026
aedd907
don't autofocus text input on advanced filters popover
bernhardoj May 12, 2026
d379f35
update hovered style
bernhardoj May 12, 2026
882d363
set key
bernhardoj May 12, 2026
1e7a9ec
update height
bernhardoj May 12, 2026
72a6a00
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 13, 2026
d2b80a3
add padding bottom to the filter list
bernhardoj May 13, 2026
b1a34bb
fix report field button spacing
bernhardoj May 13, 2026
6fe496d
autofocus only on fullscreen
bernhardoj May 13, 2026
4098768
fix pressed state stuck
bernhardoj May 13, 2026
f9f4743
fix spellcheck
bernhardoj May 13, 2026
4ca947e
disable keyboard shortcut when a modal is visible
bernhardoj May 14, 2026
ec83484
fix individual filter popup takes whole screen
bernhardoj May 14, 2026
74ba7eb
fix pressed state stuck issue
bernhardoj May 14, 2026
baf24bc
fix broken layout when keyboard shows
bernhardoj May 14, 2026
bf8a94b
fix janky animation when keyboard shows on ios
bernhardoj May 14, 2026
d3b85d0
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 14, 2026
128fd4c
fix search input skeleton height
bernhardoj May 15, 2026
05a448f
show the footer on the selection list component
bernhardoj May 15, 2026
582228b
merge main
bernhardoj May 15, 2026
8cc6c14
text input filter isn't autofocused on fullscreen
bernhardoj May 15, 2026
14e44f1
remove log
bernhardoj May 15, 2026
f1fccd3
fix button spacing
bernhardoj May 15, 2026
05a1774
fix missing type
bernhardoj May 15, 2026
6895eea
prettier
bernhardoj May 15, 2026
a2f027d
remove unused eslint-disable directive
bernhardoj May 15, 2026
85d10a1
update the triangle offset to the right more
bernhardoj May 15, 2026
c8f5afc
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 18, 2026
c15ebac
remove safe triangle when wheel event is detected
bernhardoj May 18, 2026
ace4315
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 19, 2026
aab7585
update text input based filter top padding
bernhardoj May 19, 2026
e612a42
fix report field spacing
bernhardoj May 19, 2026
fb91c05
add feed filter
bernhardoj May 19, 2026
46d86b4
remove view offset
bernhardoj May 19, 2026
e092b58
skip feed filter
bernhardoj May 19, 2026
2b5d723
compress svg
bernhardoj May 19, 2026
607de86
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 20, 2026
44f06b2
prettier
bernhardoj May 20, 2026
86ff850
fix selection jumps to the end on each change
bernhardoj May 20, 2026
e734cdd
don't dismiss keyboard when tap is handled
bernhardoj May 20, 2026
275a525
fix button bottom spacing
bernhardoj May 20, 2026
6066b6b
fix report field list is not visible on native
bernhardoj May 20, 2026
c592175
remove unused style
bernhardoj May 20, 2026
f330856
fix skeleton heigh
bernhardoj May 20, 2026
198669c
fix report field text input is covered by apply button
bernhardoj May 20, 2026
b49328e
only enables max height on selected filter view
bernhardoj May 20, 2026
6502c3e
replace Activity with simple display none switch because press out wo…
bernhardoj May 20, 2026
7b363cf
only hide on fullscreen
bernhardoj May 20, 2026
6d247c5
handle nav back
bernhardoj May 20, 2026
4458bd5
prettier
bernhardoj May 20, 2026
ca0f6a0
make it optional
bernhardoj May 20, 2026
bcd8722
remove unused import
bernhardoj May 20, 2026
f9a84cd
fix whole amount filters input is shrinked when keyboard shows in lan…
bernhardoj May 21, 2026
6060d98
prettier
bernhardoj May 21, 2026
4770830
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 22, 2026
ef26ce2
removed leftover
bernhardoj May 22, 2026
5d9570f
rename
bernhardoj May 22, 2026
1c1596e
fix triangle width exceeds the content
bernhardoj May 22, 2026
2561596
use the available width
bernhardoj May 22, 2026
ebdf655
fix selected date modifier is always cleared even though validation f…
bernhardoj May 22, 2026
be9bcf1
fix card selector is not shown when no filter is applied yet
bernhardoj May 22, 2026
3af9c38
reduce min height
bernhardoj May 22, 2026
5a05803
allow to deselect single selection filter
bernhardoj May 22, 2026
5d726a6
prettier
bernhardoj May 22, 2026
aaf94a0
lint
bernhardoj May 22, 2026
e890eec
moves to SearchQueryUtils
bernhardoj May 22, 2026
987b427
add test
bernhardoj May 22, 2026
3106286
prettier
bernhardoj May 22, 2026
793ab44
Merge branch 'main' into feat/84877-update-filters-chip-to-open-filte…
bernhardoj May 23, 2026
ad4e4bf
typo
bernhardoj May 23, 2026
5a29379
rename
bernhardoj May 23, 2026
344efe8
remove unused import
bernhardoj May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/images/coins.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/credit-card-multiple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/images/multifactorAuthentication/fingerprint.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/paycheck.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/user-arrow-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ const CONST = {
POPOVER_DATE_RANGE_WIDTH: 672,
POPOVER_DATE_MAX_HEIGHT: 366,
POPOVER_DATE_MIN_HEIGHT: 322,
ADVANCED_FILTERS_POPOVER_HEIGHT: 520,
ADVANCED_FILTERS_POPOVER_WIDTH: 582,
ADVANCED_FILTERS_CONTENT_WIDTH: 331,
TOOLTIP_ANIMATION_DURATION: 500,
DROPDOWN_SCROLL_THRESHOLD: 5,
// Multiplier for gyroscope animation in order to make it a bit more subtle
Expand Down Expand Up @@ -8469,6 +8472,7 @@ const CONST = {
ADVANCED_FILTER_ITEM: 'Search-AdvancedFilterItem',
SAVE_SEARCH_BUTTON: 'Search-SaveSearchButton',
VIEW_RESULTS_BUTTON: 'Search-ViewResultsButton',
CLEAR_FILTERS_BUTTON: 'Search-ClearFiltersButton',
ACTION_CELL_VIEW: 'Search-ActionCellView',
ACTION_CELL_PAY: 'Search-ActionCellPay',
ACTION_CELL_ACTION: 'Search-ActionCellAction',
Expand Down
6 changes: 6 additions & 0 deletions src/components/Icon/chunks/expensify-icons.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import Copy from '@assets/images/copy.svg';
import CreditCardExclamation from '@assets/images/credit-card-exclamation.svg';
import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg';
import CreditCardLock from '@assets/images/credit-card-lock.svg';
import CreditCardMultiple from '@assets/images/credit-card-multiple.svg';
import CreditCardWithPlaneHourglass from '@assets/images/credit-card-with-plane-hourglass.svg';
import CreditCardWithPlane from '@assets/images/credit-card-with-plane.svg';
import CreditCard from '@assets/images/creditcard.svg';
Expand Down Expand Up @@ -186,6 +187,7 @@ import OfflineCloud from '@assets/images/offline-cloud.svg';
import Offline from '@assets/images/offline.svg';
import Paperclip from '@assets/images/paperclip.svg';
import Pause from '@assets/images/pause.svg';
import Paycheck from '@assets/images/paycheck.svg';
import Pencil from '@assets/images/pencil.svg';
import Percent from '@assets/images/percent.svg';
import Phone from '@assets/images/phone.svg';
Expand Down Expand Up @@ -249,6 +251,7 @@ import TreasureChest from '@assets/images/treasure-chest.svg';
import Unlock from '@assets/images/unlock.svg';
import UploadAlt from '@assets/images/upload-alt.svg';
import Upload from '@assets/images/upload.svg';
import UserArrowLeft from '@assets/images/user-arrow-left.svg';
import UserCheck from '@assets/images/user-check.svg';
import UserEye from '@assets/images/user-eye.svg';
import UserLock from '@assets/images/user-lock.svg';
Expand Down Expand Up @@ -320,6 +323,7 @@ const Expensicons = {
ConnectionComplete,
Copy,
CreditCard,
CreditCardMultiple,
Crop,
CreditCardHourglass,
CreditCardExclamation,
Expand Down Expand Up @@ -417,6 +421,7 @@ const Expensicons = {
OdometerEnd,
Paperclip,
Pause,
Paycheck,
Pencil,
Percent,
Phone,
Expand Down Expand Up @@ -462,6 +467,7 @@ const Expensicons = {
Upload,
UploadAlt,
User,
UserArrowLeft,
UserCheck,
Users,
VideoSlash,
Expand Down
4 changes: 2 additions & 2 deletions src/components/PopoverWithMeasuredContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type PopoverWithMeasuredContentProps from './types';
* This component is a perf optimization, it return BOTTOM_DOCKED early, for small screens avoiding Popover measurement logic calculations.
* It defers rendering of PopoverWithMeasuredContentBase to idle time to avoid blocking more priority UI updates with measurements.
*/
function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode, ...props}: PopoverWithMeasuredContentProps) {
function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode, smallScreenModalType, ...props}: PopoverWithMeasuredContentProps) {
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();

Expand All @@ -32,7 +32,7 @@ function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottom
return (
<Modal
{...props}
type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
type={smallScreenModalType ?? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
animationIn="slideInUp"
animationOut="slideOutDown"
shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode={shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode}
Expand Down
4 changes: 4 additions & 0 deletions src/components/PopoverWithMeasuredContent/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type BaseModalProps from '@components/Modal/types';
import type PopoverProps from '@components/Popover/types';
import type {AnchorDimensions, AnchorPosition} from '@styles/index';

Expand All @@ -19,6 +20,9 @@ type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {

/** Whether to skip re-measurement when becoming visible (for components with static dimensions) */
shouldSkipRemeasurement?: boolean;

/** The modal type for modal when rendered in small screen */
smallScreenModalType?: BaseModalProps['type'];
Comment thread
bernhardoj marked this conversation as resolved.
};

export default PopoverWithMeasuredContentProps;
11 changes: 11 additions & 0 deletions src/components/SafeTriangle/index.native.tsx
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;
194 changes: 194 additions & 0 deletions src/components/SafeTriangle/index.tsx
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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-2 (docs)

0.1 is a magic number representing a cursor speed threshold (in px/ms). Its meaning and rationale are not immediately clear.

Extract it to a named constant:

/** Cursor speed threshold (px/ms) below which the safe triangle apex is updated */
const SLOW_CURSOR_SPEED_THRESHOLD = 0.1;

// usage
if (speed === null || speed < SLOW_CURSOR_SPEED_THRESHOLD) {

Reviewed at: c9f0ff0 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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}]}
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) {
Comment thread
bernhardoj marked this conversation as resolved.
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;
8 changes: 8 additions & 0 deletions src/components/SafeTriangle/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type {View} from 'react-native';
Comment thread
bernhardoj marked this conversation as resolved.

type SafeTriangleProps = {
submenuRef: React.RefObject<View | null>;
children: React.ReactNode;
};

export default SafeTriangleProps;
Loading
Loading