Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1a5dac9
Migrate YearPickerModal to a @react-navigation modal screen
trasnake87 May 19, 2026
883930e
Make YearSelector dynamic route reachable from any CalendarPicker host
trasnake87 May 19, 2026
426a100
Use Modal.closeTop for popover hosts and useId for the fallback conte…
trasnake87 May 22, 2026
cd94220
Require pickerContextID on CalendarPicker and add YEAR_SELECTOR getRoute
trasnake87 May 23, 2026
46ff704
Use createElement in CalendarPickerForTest wrapper to satisfy jsx-pro…
trasnake87 May 23, 2026
b8623b2
Keep date picker mounted while year selector is open on web wide-screen
trasnake87 Jun 2, 2026
4ddd719
Fix CalendarPicker unit test for the new root-navigation-state read
trasnake87 Jun 2, 2026
d831e3a
Address review: visibilityHidden on year-picker hide + getPlatform() …
trasnake87 Jun 6, 2026
4b66982
Merge remote-tracking branch 'upstream/main' into yearpicker-rnav-90469
trasnake87 Jun 6, 2026
57f2969
Hide whole year-picker host popover instead of inner calendar; drop r…
trasnake87 Jun 6, 2026
c9c2bea
Mock useRootNavigationState in IOU/time confirmation tests that rende…
trasnake87 Jun 7, 2026
fc49180
Merge main into yearpicker-rnav-90469
trasnake87 Jun 12, 2026
b1d1f4c
Preserve search date-filter view state across the year-selector round…
trasnake87 Jun 13, 2026
d62b9db
DOB date picker: keep popover mounted + hide CalendarPicker while yea…
trasnake87 Jun 13, 2026
b0bae95
Merge branch 'main' of https://github.com/Expensify/App into yearpick…
trasnake87 Jun 16, 2026
c623abb
Merge branch 'main' of https://github.com/Expensify/App into yearpick…
trasnake87 Jun 18, 2026
942fc3b
Make kept-mounted date popovers pointer-transparent for the year-sele…
trasnake87 Jun 20, 2026
cb2dea4
Keep date pickers open across the year-selector round-trip; address r…
trasnake87 Jun 20, 2026
dcd2401
Merge remote-tracking branch 'upstream/main' into yearpicker-rnav-90469
trasnake87 Jun 20, 2026
40748ec
Fix CI typecheck + spellcheck on the merged branch
trasnake87 Jun 20, 2026
3bb3df6
Merge remote-tracking branch 'upstream/main' into yearpicker-rnav-90469
trasnake87 Jun 26, 2026
392638d
Clear search date-modifier breadcrumb on sub-view exit
trasnake87 Jun 29, 2026
1754931
Coordinate year-selector hide through HiddenForOverlayContext
trasnake87 Jul 2, 2026
dd63b95
Merge main (Oxfmt import-order conflicts; adopt main's unwrapped dyna…
trasnake87 Jul 11, 2026
fb8a7b3
Restore imports dropped in the main merge
trasnake87 Jul 11, 2026
ff539d4
Remove imports resurrected by the merge; satisfy required swipeThresh…
trasnake87 Jul 11, 2026
c1ca658
Oxfmt the pre-migration PR files
trasnake87 Jul 11, 2026
5415295
Oxfmt remaining test file
trasnake87 Jul 11, 2026
723ffd0
Align modal context test with tightened lint rules
trasnake87 Jul 11, 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
12 changes: 12 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,16 @@ const ONYXKEYS = {
/** Stores the role selected for members being imported from a spreadsheet */
IMPORTED_SPREADSHEET_MEMBER_ROLE: 'importedSpreadsheetMemberRole',

/** Generic, `contextID`-routed transient command: the year selected in the year picker, read back and cleared by the CalendarPicker that opened it (any host: DOB, ScheduleCall, Search) */
CALENDAR_PICKER_SELECTED_YEAR: 'calendarPickerSelectedYear',

/**
* Search-only companion to `CALENDAR_PICKER_SELECTED_YEAR`: the active Search date-filter sub-view (Custom date/range modifier).
* The Search popover unmounts when the year picker screen opens, so this breadcrumb lets it restore the sub-view on return.
* Only honoured while a `search*`-context year write-back is pending, and cleared when the user leaves the sub-view.
*/
CALENDAR_PICKER_SELECTED_DATE_MODIFIER: 'calendarPickerSelectedDateModifier',

/** Stores the route to open after changing app permission from settings */
LAST_ROUTE: 'lastRoute',

Expand Down Expand Up @@ -1654,6 +1664,8 @@ type OnyxValuesMapping = {
[ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet;
[ONYXKEYS.IMPORTED_SPREADSHEET_MEMBER_DATA]: OnyxTypes.ImportedSpreadsheetMemberData[];
[ONYXKEYS.IMPORTED_SPREADSHEET_MEMBER_ROLE]: ValueOf<typeof CONST.POLICY.ROLE>;
[ONYXKEYS.CALENDAR_PICKER_SELECTED_YEAR]: {contextID: string; year: number};
[ONYXKEYS.CALENDAR_PICKER_SELECTED_DATE_MODIFIER]: string;
[ONYXKEYS.LAST_ROUTE]: string;
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record<string, string>;
Expand Down
12 changes: 12 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ const DYNAMIC_ROUTES = {
path: 'imported-members-role',
entryScreens: [SCREENS.WORKSPACE.MEMBERS_IMPORTED_CONFIRMATION],
},
YEAR_SELECTOR: {
path: 'year-selector',
queryParams: ['contextID', 'currentYear', 'minYear', 'maxYear'],
// CalendarPicker is a generic component reached from many screens (date input fields,
// DateSelectPopup, RangeDatePicker, DatePresetFilterBase, ScheduleCallPage, ...), and the
// previous in-place YearPickerModal had no screen restriction. Use '*' so the year selector
// remains reachable from every CalendarPicker host and doesn't silently break when new
// date-input screens are added (matches KEYBOARD_SHORTCUTS / EXIT_SURVEY_* generic flows).
entryScreens: ['*'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add a getRoute handler here to add queryParams.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in cd94220 — added a getRoute on YEAR_SELECTOR that takes {contextID, currentYear, minYear, maxYear} and goes through getUrlWithParams('year-selector', …) so encoding stays in one place and matches the declared queryParams.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in cd94220YEAR_SELECTOR now has a typed getRoute({contextID, currentYear, minYear, maxYear}) handler that builds the query params via getUrlWithParams.

getRoute: ({contextID, currentYear, minYear, maxYear}: {contextID: string; currentYear: number; minYear: number; maxYear: number}) =>
getUrlWithParams('year-selector', {contextID, currentYear, minYear, maxYear}),
},
REPORT_SETTINGS: {
path: 'report-settings',
entryScreens: [SCREENS.REPORT_DETAILS.DYNAMIC_ROOT],
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const SCREENS = {
HELP: 'Settings_Help',
DYNAMIC_VERIFY_ACCOUNT: 'Dynamic_Verify_Account',
DYNAMIC_ADD_BANK_ACCOUNT_VERIFY_ACCOUNT: 'Dynamic_Add_Bank_Account_Verify_Account',
DYNAMIC_YEAR_SELECTOR: 'Dynamic_Year_Selector',
DYNAMIC_EXIT_SURVEY_CONFIRM: 'Dynamic_ExitSurvey_Confirm',
DYNAMIC_EXIT_SURVEY_REASON: 'Dynamic_ExitSurvey_Reason',
DYNAMIC_KEYBOARD_SHORTCUTS: 'Dynamic_Keyboard_Shortcuts',
Expand Down
100 changes: 100 additions & 0 deletions src/components/DatePicker/CalendarPicker/DynamicYearSelectorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';

import useDynamicBackPath from '@hooks/useDynamicBackPath';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';

import {setCalendarPickerSelectedYear} from '@libs/actions/CalendarPicker';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';

import type {SettingsNavigatorParamList} from '@navigation/types';

import CONST from '@src/CONST';
import {DYNAMIC_ROUTES} from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';

import React, {useMemo, useState} from 'react';
import {Keyboard} from 'react-native';

import type CalendarPickerListItem from './types';

type DynamicYearSelectorPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.DYNAMIC_YEAR_SELECTOR>;

function DynamicYearSelectorPage({route}: DynamicYearSelectorPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.YEAR_SELECTOR.path);

const {contextID} = route.params;
const currentYear = Number(route.params.currentYear) || new Date().getFullYear();
const minYear = Number(route.params.minYear) || CONST.CALENDAR_PICKER.MIN_YEAR;
const maxYear = Number(route.params.maxYear) || CONST.CALENDAR_PICKER.MAX_YEAR;

const [searchText, setSearchText] = useState('');

const years: CalendarPickerListItem[] = useMemo(
() =>
Array.from({length: maxYear - minYear + 1}, (value, index) => index + minYear).map((year) => ({
text: year.toString(),
value: year,
keyForList: year.toString(),
isSelected: year === currentYear,
})),
[minYear, maxYear, currentYear],
);

const {data, headerMessage} = useMemo(() => {
const yearsList = searchText === '' ? years : years.filter((year) => year.text?.includes(searchText));
return {
headerMessage: !yearsList.length ? translate('common.noResultsFound') : '',
data: yearsList.sort((a, b) => b.value - a.value),
};
}, [years, searchText, translate]);

const textInputOptions = useMemo(
() => ({
label: translate('yearPickerPage.selectYear'),
value: searchText,
onChangeText: (text: string) => setSearchText(text.replaceAll(CONST.REGEX.NON_NUMERIC, '').trim()),
headerMessage,
maxLength: 4,
inputMode: CONST.INPUT_MODE.NUMERIC,
}),
[headerMessage, searchText, translate],
);

return (
<ScreenWrapper
style={[styles.pb0]}
includePaddingTop={false}
enableEdgeToEdgeBottomSafeAreaPadding
testID="DynamicYearSelectorPage"
>
<HeaderWithBackButton
title={translate('yearPickerPage.year')}
onBackButtonPress={() => Navigation.goBack(backPath)}
/>
<SelectionList
data={data}
ListItem={SingleSelectListItem}
onSelectRow={(option) => {
Keyboard.dismiss();
setCalendarPickerSelectedYear(contextID, option.value);
Navigation.goBack(backPath);
}}
textInputOptions={textInputOptions}
initiallyFocusedItemKey={currentYear.toString()}
disableMaintainingScrollPosition
addBottomSafeAreaPadding
shouldStopPropagation
showScrollIndicator
/>
</ScreenWrapper>
);
}

export default DynamicYearSelectorPage;
109 changes: 0 additions & 109 deletions src/components/DatePicker/CalendarPicker/YearPickerModal.tsx

This file was deleted.

Loading
Loading