diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7050360c2e8e..8130c271a2db 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -131,6 +131,16 @@ const ROUTES = { route: 'settings/profile/address/country', getRoute: (country: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/address/country?country=${country}`, backTo), }, + SETTINGS_ADDRESS_STATE: { + route: 'settings/profile/address/state', + + getRoute: (state?: string, backTo?: string, label?: string) => + `${getUrlWithBackToParam(`settings/profile/address/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + // the label param can be an empty string so we cannot use a nullish ?? operator + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' + }` as const, + }, SETTINGS_CONTACT_METHODS: { route: 'settings/profile/contact-methods', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/profile/contact-methods', backTo), @@ -401,6 +411,16 @@ const ROUTES = { `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, }, + MONEY_REQUEST_STATE_SELECTOR: { + route: 'request/state', + + getRoute: (state?: string, backTo?: string, label?: string) => + `${getUrlWithBackToParam(`request/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + // the label param can be an empty string so we cannot use a nullish ?? operator + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' + }` as const, + }, IOU_REQUEST: 'request/new', IOU_SEND: 'send/new', IOU_SEND_ADD_BANK_ACCOUNT: 'send/new/add-bank-account', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c732594cdcbe..cf864fd96b3e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -56,6 +56,7 @@ const SCREENS = { DATE_OF_BIRTH: 'Settings_DateOfBirth', ADDRESS: 'Settings_Address', ADDRESS_COUNTRY: 'Settings_Address_Country', + ADDRESS_STATE: 'Settings_Address_State', }, PREFERENCES: { @@ -154,6 +155,7 @@ const SCREENS = { WAYPOINT: 'Money_Request_Waypoint', EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint', RECEIPT: 'Money_Request_Receipt', + STATE_SELECTOR: 'Money_Request_State_Selector', }, IOU_SEND: { diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 626da4cfd5d4..a4d1c2dd6bd6 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -16,7 +16,7 @@ import CountrySelector from './CountrySelector'; import FormProvider from './Form/FormProvider'; import InputWrapper from './Form/InputWrapper'; import type {FormOnyxValues} from './Form/types'; -import StatePicker from './StatePicker'; +import StateSelector from './StateSelector'; import TextInput from './TextInput'; type CountryZipRegex = { @@ -190,7 +190,7 @@ function AddressForm({ {isUSAForm ? ( void; - - /** Function to call when the user closes the State modal */ - onClose?: () => void; - - /** The search value from the selection list */ - searchValue: string; - - /** Function to call when the user types in the search input */ - setSearchValue: (value: string) => void; - - /** Label to display on field */ - label?: string; -}; - -function StateSelectorModal({currentState, isVisible, onClose = () => {}, onStateSelected = () => {}, searchValue, setSearchValue, label}: StateSelectorModalProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - useEffect(() => { - if (isVisible) { - return; - } - setSearchValue(''); - }, [isVisible, setSearchValue]); - - const countryStates: CountryData[] = useMemo( - () => - Object.keys(COMMON_CONST.STATES).map((state) => { - const stateName = translate(`allStates.${state as State}.stateName`); - const stateISO = translate(`allStates.${state as State}.stateISO`); - return { - value: stateISO, - keyForList: stateISO, - text: stateName, - isSelected: currentState === stateISO, - searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), - }; - }), - [translate, currentState], - ); - - const searchResults = searchCountryOptions(searchValue, countryStates); - const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; - - return ( - - - - - - - ); -} - -StateSelectorModal.displayName = 'StateSelectorModal'; - -export default StateSelectorModal; -export type {State}; diff --git a/src/components/StatePicker/index.tsx b/src/components/StatePicker/index.tsx deleted file mode 100644 index 72262346b0d7..000000000000 --- a/src/components/StatePicker/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import React, {useState} from 'react'; -import type {ForwardedRef} from 'react'; -import {View} from 'react-native'; -import FormHelpMessage from '@components/FormHelpMessage'; -import type {MenuItemProps} from '@components/MenuItem'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type {MaybePhraseKey} from '@libs/Localize'; -import type {CountryData} from '@libs/searchCountryOptions'; -import StateSelectorModal from './StateSelectorModal'; -import type {State} from './StateSelectorModal'; - -type StatePickerProps = { - /** Error text to display */ - errorText?: MaybePhraseKey; - - /** State to display */ - value?: State; - - /** Callback to call when the input changes */ - onInputChange?: (value: string) => void; - - /** Label to display on field */ - label?: string; - - /** Any additional styles to apply */ - wrapperStyle?: MenuItemProps['wrapperStyle']; - - /** Callback to call when the picker modal is dismissed */ - onBlur?: () => void; -}; - -function StatePicker({value, onInputChange, label, onBlur, errorText = '', wrapperStyle}: StatePickerProps, ref: ForwardedRef) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const [isPickerVisible, setIsPickerVisible] = useState(false); - const [searchValue, setSearchValue] = useState(''); - - const showPickerModal = () => { - setIsPickerVisible(true); - }; - - const hidePickerModal = (shouldBlur = true) => { - if (shouldBlur) { - onBlur?.(); - } - setIsPickerVisible(false); - }; - - const updateStateInput = (state: CountryData) => { - if (state.value !== value) { - onInputChange?.(state.value); - } - // If the user selects any state, call the hidePickerModal function with shouldBlur = false - // to prevent the onBlur function from being called. - hidePickerModal(false); - }; - - const title = value && Object.keys(COMMON_CONST.STATES).includes(value) ? translate(`allStates.${value}.stateName`) : ''; - const descStyle = title.length === 0 ? styles.textNormal : null; - - return ( - - - - - - - - ); -} - -StatePicker.displayName = 'StatePicker'; - -export default React.forwardRef(StatePicker); diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx new file mode 100644 index 000000000000..aa458834c8ad --- /dev/null +++ b/src/components/StateSelector.tsx @@ -0,0 +1,107 @@ +import {useIsFocused} from '@react-navigation/native'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import React, {useEffect, useRef} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {MaybePhraseKey} from '@libs/Localize'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; +import FormHelpMessage from './FormHelpMessage'; +import type {MenuItemProps} from './MenuItem'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; + +type State = keyof typeof COMMON_CONST.STATES; + +type StateSelectorProps = { + /** Form error text. e.g when no state is selected */ + errorText?: MaybePhraseKey; + + /** Current selected state */ + value?: State | ''; + + /** Callback to call when the input changes */ + onInputChange?: (value: string) => void; + + /** Label to display on field */ + label?: string; + + /** Any additional styles to apply */ + wrapperStyle?: MenuItemProps['wrapperStyle']; + + /** Callback to call when the picker modal is dismissed */ + onBlur?: () => void; + + /** object to get route details from */ + stateSelectorRoute?: typeof ROUTES.SETTINGS_ADDRESS_STATE | typeof ROUTES.MONEY_REQUEST_STATE_SELECTOR; +}; + +function StateSelector( + {errorText, onBlur, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE}: StateSelectorProps, + ref: ForwardedRef, +) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const stateFromUrl = useGeographicalStateFromRoute(); + + const didOpenStateSelector = useRef(false); + const isFocused = useIsFocused(); + + useEffect(() => { + // Check if the state selector was opened and no value was selected, triggering onBlur to display an error + if (isFocused && didOpenStateSelector.current) { + didOpenStateSelector.current = false; + if (!stateFromUrl) { + onBlur?.(); + } + } + + // If no state is selected from the URL, exit the effect early to avoid further processing. + if (!stateFromUrl) { + return; + } + + // If a state is selected, invoke `onInputChange` to update the form and clear any validation errors related to the state selection. + if (onInputChange) { + onInputChange(stateFromUrl); + } + + // Clears the `state` parameter from the URL to ensure the component state is driven by the parent component rather than URL parameters. + // This helps prevent issues where the component might not update correctly if the state is controlled by both the parent and the URL. + Navigation.setParams({state: undefined}); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateFromUrl, onBlur, isFocused]); + + const title = stateCode && Object.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + + return ( + + { + const activeRoute = Navigation.getActiveRoute(); + didOpenStateSelector.current = true; + Navigation.navigate(stateSelectorRoute.getRoute(stateCode, activeRoute, label)); + }} + wrapperStyle={wrapperStyle} + /> + + + + + ); +} + +StateSelector.displayName = 'StateSelector'; + +export default React.forwardRef(StateSelector); diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts new file mode 100644 index 000000000000..13936ee78f5b --- /dev/null +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -0,0 +1,23 @@ +import {useRoute} from '@react-navigation/native'; +import type {ParamListBase, RouteProp} from '@react-navigation/native'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; + +type CustomParamList = ParamListBase & Record>; +type State = keyof typeof COMMON_CONST.STATES; + +/** + * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. + * Example 1: Url: https://new.expensify.com/settings/profile/address?state=MO Returns: MO + * Example 2: Url: https://new.expensify.com/settings/profile/address?state=ASDF Returns: undefined + * Example 3: Url: https://new.expensify.com/settings/profile/address Returns: undefined + * Example 4: Url: https://new.expensify.com/settings/profile/address?state=MO-hash-a12341 Returns: MO + */ +export default function useGeographicalStateFromRoute(stateParamName = 'state'): State | undefined { + const route = useRoute>(); + const stateFromUrlTemp = route.params?.[stateParamName] as string | undefined; + + if (!stateFromUrlTemp) { + return; + } + return COMMON_CONST.STATES[stateFromUrlTemp as State]?.stateISO; +} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 55c58290b1cd..0d1b9b59a089 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -94,6 +94,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.WAYPOINT]: () => require('../../../../pages/iou/MoneyRequestWaypointPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.RECEIPT]: () => require('../../../../pages/EditRequestReceiptPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: () => require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default as React.ComponentType, }); const SplitDetailsModalStackNavigator = createModalStackNavigator({ @@ -189,6 +190,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: () => require('../../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default as React.ComponentType, + [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: () => require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: () => require('../../../../pages/settings/Profile/Contacts/NewContactMethodPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f272ae24973a..823b6514c42b 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -212,6 +212,10 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ADDRESS_COUNTRY.route, exact: true, }, + [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: { + path: ROUTES.SETTINGS_ADDRESS_STATE.route, + exact: true, + }, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: { path: ROUTES.SETTINGS_2FA.route, exact: true, @@ -527,6 +531,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, [SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route, [SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route, + [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Url.ts b/src/libs/Url.ts index a21f007e8468..4e3282e7bdb3 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -41,4 +41,19 @@ function hasSameExpensifyOrigin(url1: string, url2: string): boolean { } } -export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL}; +/** + * Appends or updates a query parameter in a given URL. + */ +function appendParam(url: string, paramName: string, paramValue: string) { + // If parameter exists, replace it + if (url.includes(`${paramName}=`)) { + const regex = new RegExp(`${paramName}=([^&]*)`); + return url.replace(regex, `${paramName}=${paramValue}`); + } + + // If parameter doesn't exist, append it + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${paramName}=${paramValue}`; +} + +export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam}; diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index dc95d62b6afa..d20d9df1d189 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -3,7 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import AddressSearch from '@components/AddressSearch'; import InputWrapper from '@components/Form/InputWrapper'; -import StatePicker from '@components/StatePicker'; +import StateSelector from '@components/StateSelector'; import TextInput from '@components/TextInput'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -128,7 +128,7 @@ function AddressForm(props) { {translate('businessInfoStep.pleaseSelectTheStateYourCompanyWasIncorporatedIn')} privatePersonalDetails?.address, [privatePersonalDetails]); - const countryFromUrl = route.params?.country; + const countryFromUrlTemp = route?.params?.country; + + // Check if country is valid + const countryFromUrl = CONST.ALL_COUNTRIES[countryFromUrlTemp as keyof typeof CONST.ALL_COUNTRIES] ? countryFromUrlTemp : ''; + const stateFromUrl = useGeographicalStateFromRoute(); const [currentCountry, setCurrentCountry] = useState(address?.country); const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; const [street1, street2] = (address?.street ?? '').split('\n'); @@ -99,6 +105,13 @@ function AddressPage({privatePersonalDetails, route}: AddressPageProps) { handleAddressChange(countryFromUrl, 'country'); }, [countryFromUrl, handleAddressChange]); + useEffect(() => { + if (!stateFromUrl) { + return; + } + handleAddressChange(stateFromUrl, 'state'); + }, [handleAddressChange, stateFromUrl]); + return ( + Object.keys(COMMON_CONST.STATES).map((state) => { + const stateName = translate(`allStates.${state as State}.stateName`); + const stateISO = translate(`allStates.${state as State}.stateISO`); + return { + value: stateISO, + keyForList: stateISO, + text: stateName, + isSelected: currentState === stateISO, + searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), + }; + }), + + [translate, currentState], + ); + + const searchResults = searchCountryOptions(searchValue, countryStates); + const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; + + const selectCountryState = useCallback( + (option: CountryData) => { + const backTo = params?.backTo ?? ''; + + // Determine navigation action based on "backTo" presence and route stack length. + if (navigation.getState()?.routes.length === 1) { + // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). + if (_.isEmpty(backTo)) { + // No "backTo": default back navigation. + Navigation.goBack(); + } else { + // "backTo" provided: navigate back to "backTo" with state parameter. + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + } + } else if (!_.isEmpty(backTo)) { + // Most common case: Navigation stack has multiple routes and "backTo" is defined: navigate to "backTo" with state parameter. + Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); + } else { + // This is a fallback block and should never execute if StateSelector is correctly appending the "backTo" route. + // Navigation stack has multiple routes but no "backTo" defined: default back navigation. + Navigation.goBack(); + } + }, + [navigation, params?.backTo], + ); + + return ( + + { + const backTo = params?.backTo ?? ''; + let backToRoute = ''; + + if (backTo) { + backToRoute = appendParam(backTo, 'state', currentState ?? ''); + } + + // @ts-expect-error Navigation.goBack does take a param + Navigation.goBack(backToRoute); + }} + /> + {/* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096 */} + + + + + ); +} + +StateSelectionPage.displayName = 'StateSelectionPage'; + +export default StateSelectionPage; diff --git a/src/pages/settings/Wallet/AddDebitCardPage.tsx b/src/pages/settings/Wallet/AddDebitCardPage.tsx index 17465e0c0715..0beb3c16018d 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.tsx +++ b/src/pages/settings/Wallet/AddDebitCardPage.tsx @@ -1,3 +1,4 @@ +import {useRoute} from '@react-navigation/native'; import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -10,7 +11,7 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; -import StatePicker from '@components/StatePicker'; +import StateSelector from '@components/StateSelector'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import TextLink from '@components/TextLink'; @@ -22,6 +23,8 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import type {AddDebitCardForm} from '@src/types/form'; import INPUT_IDS from '@src/types/form/AddDebitCardForm'; @@ -58,6 +61,7 @@ function DebitCardPage({formData}: DebitCardPageProps) { const {translate} = useLocalize(); const prevFormDataSetupComplete = usePrevious(!!formData?.setupComplete); const nameOnCardRef = useRef(null); + const route = useRoute(); /** * Reset the form values on the mount and unmount so that old errors don't show when this form is displayed again. @@ -200,7 +204,8 @@ function DebitCardPage({formData}: DebitCardPageProps) { /> diff --git a/src/stories/Form.stories.tsx b/src/stories/Form.stories.tsx index 8eeab971ea88..a2bcfe1db03f 100644 --- a/src/stories/Form.stories.tsx +++ b/src/stories/Form.stories.tsx @@ -8,7 +8,7 @@ import FormProvider from '@components/Form/FormProvider'; import type {FormProviderProps} from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import Picker from '@components/Picker'; -import StatePicker from '@components/StatePicker'; +import StateSelector from '@components/StateSelector'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import type {MaybePhraseKey} from '@libs/Localize'; @@ -50,7 +50,7 @@ const story: ComponentMeta = { AddressSearch, CheckboxWithLabel, Picker, - StatePicker, + StateSelector, DatePicker, }, }; @@ -148,7 +148,7 @@ function Template(props: FormProviderProps) { />