src/phone-input/base-country-picker.tsx (216 lines of code) (raw):

/* Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ import type { ComponentProps } from 'react'; import React, { useState } from 'react'; import { StyledRoot, StyledFlagContainer, StyledCountrySelectDropdownListItem as DefaultListItem, StyledCountrySelectDropdownFlagColumn as DefaultFlagColumn, StyledCountrySelectDropdownNameColumn as DefaultNameColumn, StyledCountrySelectDropdownDialcodeColumn as DefaultDialcodeColumn, } from './styled-components'; import { SingleSelect as DefaultSelect } from '../select'; import { PLACEMENT } from '../popover'; import { getOverrides, mergeOverrides } from '../helpers/overrides'; import defaultProps from './default-props'; import { iso2FlagEmoji } from './utils'; import type { Country, CountrySelectProps } from './types'; CountryPicker.defaultProps = { disabled: defaultProps.disabled, inputRef: { current: null }, maxDropdownHeight: defaultProps.maxDropdownHeight, maxDropdownWidth: defaultProps.maxDropdownWidth, overrides: {}, size: defaultProps.size, error: defaultProps.error, positive: defaultProps.positive, required: defaultProps.required, }; const DropdownListItem = React.forwardRef<HTMLLIElement, ComponentProps<typeof DefaultListItem>>( (props, ref) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { children, ...rest } = props; return ( <DefaultListItem ref={ref} {...rest}> {props.children} </DefaultListItem> ); } ); DropdownListItem.displayName = 'DropdownListItem'; // @ts-ignore function DropdownOptionContent(props) { return <>{props.children}</>; } export default function CountryPicker(props: CountrySelectProps) { const { country, disabled, error, inputRef, maxDropdownHeight, maxDropdownWidth, mapIsoToLabel, onCountryChange, overrides, positive, required, size, } = props; const [resetScrollIndex, setResetScrollIndex] = useState(false); const sharedProps = { $disabled: disabled, $error: error, $positive: positive, $required: required, $size: size, }; const options = Object.values(props.countries); const scrollIndex = options.findIndex((opt) => opt.id === country.id); const baseSelectOverrides = { Root: { component: StyledRoot, }, Input: { style: { width: 0, }, props: { // https://github.com/uber/baseweb/issues/3846 autoComplete: 'chrome-off', }, }, IconsContainer: { style: { paddingRight: '0', }, }, SingleValue: { style: { display: 'flex', alignItems: 'center', }, }, StatefulMenu: { props: { // @ts-ignore stateReducer: (type, nextState) => { const next = { ...nextState, highlightedIndex: resetScrollIndex ? 0 : nextState.highlightedIndex, }; setResetScrollIndex(false); return next; }, initialState: { isFocused: true, highlightedIndex: scrollIndex, }, }, }, DropdownContainer: { style: { width: maxDropdownWidth, maxWidth: 'calc(100vw - 10px)', }, }, Dropdown: { props: { $country: country, $maxDropdownHeight: maxDropdownHeight, $mapIsoToLabel: mapIsoToLabel, $overrides: { CountrySelectDropdown: overrides.CountrySelectDropdown, CountrySelectDropdownListItem: overrides.CountrySelectDropdownListItem, CountrySelectDropdownFlagColumn: overrides.CountrySelectDropdownFlagColumn, CountrySelectDropdownNameColumn: overrides.CountrySelectDropdownNameColumn, CountrySelectDropdownDialcodeColumn: overrides.CountrySelectDropdownDialcodeColumn, FlagContainer: overrides.FlagContainer, }, }, }, DropdownListItem: { component: DropdownListItem, }, OptionContent: { component: DropdownOptionContent, }, Popover: { props: { focusLock: false, placement: PLACEMENT.bottomLeft, }, }, }; const [Select, selectProps] = getOverrides(overrides.CountrySelect, DefaultSelect); const selectOverrides = mergeOverrides(baseSelectOverrides, { Dropdown: overrides.CountrySelectDropdown || {}, DropdownListItem: overrides.CountrySelectDropdownListItem || {}, }); selectProps.overrides = mergeOverrides(selectOverrides, selectProps.overrides); const [FlagColumn, flagColumnProps] = getOverrides( overrides.CountrySelectDropdownFlagColumn, DefaultFlagColumn ); const [FlagContainer, flagContainerProps] = getOverrides( overrides.FlagContainer, StyledFlagContainer ); const [NameColumn, nameColumnProps] = getOverrides( overrides.CountrySelectDropdownNameColumn, DefaultNameColumn ); const [Dialcode, dialcodeProps] = getOverrides( overrides.CountrySelectDropdownDialcodeColumn, DefaultDialcodeColumn ); return ( <Select clearable={false} disabled={disabled} // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore getOptionLabel={({ option, optionState }) => { const iso = option.id; return ( <> <FlagColumn {...flagColumnProps}> <FlagContainer $iso={iso} data-iso={iso} {...flagContainerProps}> {iso2FlagEmoji(iso)} </FlagContainer> </FlagColumn> <NameColumn {...nameColumnProps}> {mapIsoToLabel ? mapIsoToLabel(iso) : option.label} </NameColumn> <Dialcode {...dialcodeProps}>{option.dialCode}</Dialcode> </> ); }} getValueLabel={(value: { option: Country }) => { const iso = value.option.id; return ( <FlagContainer $iso={iso} data-iso={iso} {...sharedProps} {...flagContainerProps}> {iso2FlagEmoji(iso)} </FlagContainer> ); }} error={error} maxDropdownHeight={maxDropdownHeight} // @ts-ignore onChange={(event) => { if (typeof onCountryChange === 'function') { onCountryChange(event); } else if (__DEV__) { console.warn( 'CountryPicker component is controlled (or stateless) ' + 'and requires an `onCountryChange` handler to be passed in ' + 'that handles the `country` prop value update.' ); } // After choosing a country, shift focus to the text input if (inputRef && inputRef.current) { inputRef.current.focus(); } }} options={options} positive={positive} aria-label="Select country" required={required} size={size} value={[country]} onInputChange={() => { setResetScrollIndex(true); }} {...selectProps} /> ); }