import type { AvatarLayoutProps } from '../../avatarLayout'; import Button from '../../button'; import { SelectInput, SelectInputOptionContent, SelectInputTriggerButton, } from '../../inputs/SelectInput'; import { CurrencyType, Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput'; import { ChevronDown } from '@transferwise/icons'; import { Flag } from '@wise/art'; import { type ButtonHTMLAttributes, forwardRef, type MouseEventHandler, useMemo, useState, } from 'react'; import { useIntl } from 'react-intl'; import messages from '../ExpressiveMoneyInput.messages'; import { getLocaleCurrencyName } from '../../common'; import { formatCurrencyCode } from '@transferwise/formatting'; export interface CurrencyOption { label?: string; code: string; keywords: string[] | undefined; } export interface CurrencySection { title: string; currencies: CurrencyOption[]; } export type CurrencyOptions = CurrencySection[]; export type Props = { id: string; labelId: string; options?: CurrencyOptions; onChange?: (currency: CurrencyType) => void; onOpen?: () => void; addons?: AvatarLayoutProps['avatars']; onSearchChange?: (payload: { query: string; resultCount: number }) => void; } & Pick; export const CurrencySelector = ({ id, currency, options = [], labelId, onChange, addons, onOpen, onSearchChange, }: Props) => { const intl = useIntl(); const allCurrencyOptions = useMemo(() => getUniqueCurrencies(options), [options]); const activeCurrencyOption = useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return allCurrencyOptions.find((option) => option.code === currency)!; }, [currency, allCurrencyOptions]); const disabled = !onChange || options.length === 0 || (options.length === 1 && options[0].currencies.length <= 1); const [searchQuery, setSearchQuery] = useState(''); const handleTriggerClick: MouseEventHandler = (event) => { const triggerEl = event.currentTarget; if (triggerEl?.getAttribute('aria-expanded') === 'false') { onOpen?.(); } }; const items = searchQuery ? filterAndSortCurrenciesForQuery(allCurrencyOptions, searchQuery).map(getCurrencySelectOption) : options.map(getCurrencyGroup); return ( { return ( } /> ); }} renderTrigger={() => ( 1 ? { ...addons[1] } : { asset: , }), }, ] .filter(Boolean) .filter((avatar) => !(avatar && Object.keys(avatar).length === 0)), }} addonEnd={disabled ? undefined : { type: 'icon', value: }} onClick={(event) => handleTriggerClick(event)} > <> {formatCurrencyCode(currency)} {getLocaleCurrencyName(intl, currency)} )} onChange={(newValue) => { onChange?.(newValue.code); }} onFilterChange={({ queryNormalized }) => { setSearchQuery(queryNormalized ?? ''); if (queryNormalized) { onSearchChange?.({ query: queryNormalized, resultCount: filterAndSortCurrenciesForQuery(allCurrencyOptions, queryNormalized) .length, }); } }} /> ); }; export const ButtonInput = forwardRef(function ButtonInput( { children, ...rest }: React.PropsWithChildren>, ref: React.ForwardedRef, ) { return ( ); }); const getCurrencySelectOption = (currency: CurrencyOption) => { return { type: 'option' as const, value: currency, filterMatchers: currency.keywords, }; }; const getCurrencyGroup = (section: CurrencySection) => { return { type: 'group' as const, label: section.title, options: section.currencies.map(getCurrencySelectOption), }; }; const getUniqueCurrencies = (options: CurrencyOptions) => { const allCurrencyOptions = options.flatMap((section) => section.currencies); const uniqueCurrencies = new Map(); allCurrencyOptions.forEach((currencyObj) => { uniqueCurrencies.set(currencyObj.code, currencyObj); }); return Array.from(uniqueCurrencies.values()); }; const filterAndSortCurrenciesForQuery = ( currencies: CurrencyOption[], query: string, ): CurrencyOption[] => { return ( currencies .filter((currency) => { return ( formatCurrencyCode(currency.code).toLowerCase().includes(query) || (currency.label ?? '').toLowerCase().includes(query) || currency.keywords?.some((keyword) => keyword.toLowerCase().includes(query)) ); }) // prefer exact matches, then sort alphabetically by code .sort((a, b) => { const aCode = a.code.toLowerCase(); const bCode = b.code.toLowerCase(); if (aCode === query) { return -1; } if (bCode === query) { return 1; } return aCode.localeCompare(bCode); }) ); };