feat: address composite field (#4492)
Added new Address field input type. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
export type IconComponent = FunctionComponent<{
|
||||
export type IconComponentProps = {
|
||||
className?: string;
|
||||
color?: string;
|
||||
size?: number;
|
||||
stroke?: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type IconComponent = FunctionComponent<IconComponentProps>;
|
||||
|
||||
@ -0,0 +1,254 @@
|
||||
import { RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { flip, offset, useFloating } from '@floating-ui/react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { FieldAddressDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CountrySelect } from '@/ui/input/components/internal/country/components/CountrySelect';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledAddressContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
|
||||
padding: 4px 8px;
|
||||
|
||||
width: 100%;
|
||||
min-width: 260px;
|
||||
> div {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHalfRowContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
export type AddressInputProps = {
|
||||
value: FieldAddressValue;
|
||||
onTab: (newAddress: FieldAddressDraftValue) => void;
|
||||
onShiftTab: (newAddress: FieldAddressDraftValue) => void;
|
||||
onEnter: (newAddress: FieldAddressDraftValue) => void;
|
||||
onEscape: (newAddress: FieldAddressDraftValue) => void;
|
||||
onClickOutside: (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newAddress: FieldAddressDraftValue,
|
||||
) => void;
|
||||
hotkeyScope: string;
|
||||
clearable?: boolean;
|
||||
onChange?: (updatedValue: FieldAddressDraftValue) => void;
|
||||
};
|
||||
|
||||
export const AddressInput = ({
|
||||
value,
|
||||
hotkeyScope,
|
||||
onTab,
|
||||
onShiftTab,
|
||||
onEnter,
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
onChange,
|
||||
}: AddressInputProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
const addressStreet1InputRef = useRef<HTMLInputElement>(null);
|
||||
const addressStreet2InputRef = useRef<HTMLInputElement>(null);
|
||||
const addressCityInputRef = useRef<HTMLInputElement>(null);
|
||||
const addressStateInputRef = useRef<HTMLInputElement>(null);
|
||||
const addressPostCodeInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const inputRefs: {
|
||||
[key in keyof FieldAddressDraftValue]?: RefObject<HTMLInputElement>;
|
||||
} = {
|
||||
addressStreet1: addressStreet1InputRef,
|
||||
addressStreet2: addressStreet2InputRef,
|
||||
addressCity: addressCityInputRef,
|
||||
addressState: addressStateInputRef,
|
||||
addressPostcode: addressPostCodeInputRef,
|
||||
};
|
||||
|
||||
const [focusPosition, setFocusPosition] =
|
||||
useState<keyof FieldAddressDraftValue>('addressStreet1');
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { refs, floatingStyles } = useFloating({
|
||||
placement: 'top-start',
|
||||
middleware: [
|
||||
flip(),
|
||||
offset({
|
||||
mainAxis: theme.spacingMultiplicator * 2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const getChangeHandler =
|
||||
(field: keyof FieldAddressDraftValue) => (updatedAddressPart: string) => {
|
||||
const updatedAddress = { ...value, [field]: updatedAddressPart };
|
||||
setInternalValue(updatedAddress);
|
||||
onChange?.(updatedAddress);
|
||||
};
|
||||
|
||||
const getFocusHandler = (fieldName: keyof FieldAddressDraftValue) => () => {
|
||||
setFocusPosition(fieldName);
|
||||
|
||||
inputRefs[fieldName]?.current?.focus();
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
'tab',
|
||||
() => {
|
||||
const currentFocusPosition = Object.keys(inputRefs).findIndex(
|
||||
(key) => key === focusPosition,
|
||||
);
|
||||
const maxFocusPosition = Object.keys(inputRefs).length - 1;
|
||||
|
||||
const nextFocusPosition = currentFocusPosition + 1;
|
||||
|
||||
const isFocusPositionAfterLast = nextFocusPosition > maxFocusPosition;
|
||||
|
||||
if (isFocusPositionAfterLast) {
|
||||
onTab?.(internalValue);
|
||||
} else {
|
||||
const nextFocusFieldName = Object.keys(inputRefs)[
|
||||
nextFocusPosition
|
||||
] as keyof FieldAddressDraftValue;
|
||||
|
||||
setFocusPosition(nextFocusFieldName);
|
||||
inputRefs[nextFocusFieldName]?.current?.focus();
|
||||
}
|
||||
},
|
||||
hotkeyScope,
|
||||
[onTab, internalValue, focusPosition],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'shift+tab',
|
||||
() => {
|
||||
const currentFocusPosition = Object.keys(inputRefs).findIndex(
|
||||
(key) => key === focusPosition,
|
||||
);
|
||||
|
||||
const nextFocusPosition = currentFocusPosition - 1;
|
||||
|
||||
const isFocusPositionBeforeFirst = nextFocusPosition < 0;
|
||||
|
||||
if (isFocusPositionBeforeFirst) {
|
||||
onShiftTab?.(internalValue);
|
||||
} else {
|
||||
const nextFocusFieldName = Object.keys(inputRefs)[
|
||||
nextFocusPosition
|
||||
] as keyof FieldAddressDraftValue;
|
||||
|
||||
setFocusPosition(nextFocusFieldName);
|
||||
inputRefs[nextFocusFieldName]?.current?.focus();
|
||||
}
|
||||
},
|
||||
hotkeyScope,
|
||||
[onTab, internalValue, focusPosition],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
onEnter(internalValue);
|
||||
},
|
||||
hotkeyScope,
|
||||
[onEnter, internalValue],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
onEscape(internalValue);
|
||||
},
|
||||
hotkeyScope,
|
||||
[onEscape, internalValue],
|
||||
);
|
||||
|
||||
const { useListenClickOutside } = useClickOutsideListener('addressInput');
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [wrapperRef],
|
||||
callback: (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
onClickOutside?.(event, internalValue);
|
||||
},
|
||||
enabled: isDefined(onClickOutside),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div ref={refs.setFloating} style={floatingStyles}>
|
||||
<StyledAddressContainer ref={wrapperRef}>
|
||||
<TextInput
|
||||
autoFocus
|
||||
value={internalValue.addressStreet1 ?? ''}
|
||||
ref={inputRefs['addressStreet1']}
|
||||
label="ADDRESS 1"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressStreet1')}
|
||||
onFocus={getFocusHandler('addressStreet1')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<TextInput
|
||||
value={internalValue.addressStreet2 ?? ''}
|
||||
ref={inputRefs['addressStreet2']}
|
||||
label="ADDRESS 2"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressStreet2')}
|
||||
onFocus={getFocusHandler('addressStreet2')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<StyledHalfRowContainer>
|
||||
<TextInput
|
||||
value={internalValue.addressCity ?? ''}
|
||||
ref={inputRefs['addressCity']}
|
||||
label="CITY"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressCity')}
|
||||
onFocus={getFocusHandler('addressCity')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<TextInput
|
||||
value={internalValue.addressState ?? ''}
|
||||
ref={inputRefs['addressState']}
|
||||
label="STATE"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressState')}
|
||||
onFocus={getFocusHandler('addressState')}
|
||||
disableHotkeys
|
||||
/>
|
||||
</StyledHalfRowContainer>
|
||||
<StyledHalfRowContainer>
|
||||
<TextInput
|
||||
value={internalValue.addressPostcode ?? ''}
|
||||
ref={inputRefs['addressPostcode']}
|
||||
label="POST CODE"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressPostcode')}
|
||||
onFocus={getFocusHandler('addressPostcode')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<CountrySelect
|
||||
onChange={getChangeHandler('addressCountry')}
|
||||
selectedCountryName={internalValue.addressCountry ?? ''}
|
||||
/>
|
||||
</StyledHalfRowContainer>
|
||||
</StyledAddressContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -13,7 +13,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { StyledInput } from './TextInput';
|
||||
import { StyledTextInput } from './TextInput';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -174,7 +174,7 @@ export const DoubleTextInput = ({
|
||||
|
||||
return (
|
||||
<StyledContainer ref={containerRef}>
|
||||
<StyledInput
|
||||
<StyledTextInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
onFocus={() => setFocusPosition('left')}
|
||||
@ -188,7 +188,7 @@ export const DoubleTextInput = ({
|
||||
handleOnPaste(event)
|
||||
}
|
||||
/>
|
||||
<StyledInput
|
||||
<StyledTextInput
|
||||
autoComplete="off"
|
||||
onFocus={() => setFocusPosition('right')}
|
||||
ref={secondValueInputRef}
|
||||
|
||||
@ -3,7 +3,7 @@ import ReactPhoneNumberInput from 'react-phone-number-input';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { CountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/CountryPickerDropdownButton';
|
||||
import { PhoneCountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton';
|
||||
|
||||
import 'react-phone-number-input/style.css';
|
||||
|
||||
@ -102,7 +102,7 @@ export const PhoneInput = ({
|
||||
onChange={handleChange}
|
||||
international={true}
|
||||
withCountryCallingCode={true}
|
||||
countrySelectComponent={CountryPickerDropdownButton}
|
||||
countrySelectComponent={PhoneCountryPickerDropdownButton}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
export const StyledTextInput = styled.input`
|
||||
margin: 0;
|
||||
${TEXT_INPUT_STYLE}
|
||||
width: 100%;
|
||||
@ -60,7 +60,7 @@ export const TextInput = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledInput
|
||||
<StyledTextInput
|
||||
autoComplete="off"
|
||||
ref={wrapperRef}
|
||||
placeholder={placeholder}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { StyledInput } from '@/ui/field/input/components/TextInput';
|
||||
import { StyledTextInput as UIStyledTextInput } from '@/ui/field/input/components/TextInput';
|
||||
import { ComputeNodeDimensions } from '@/ui/utilities/dimensions/components/ComputeNodeDimensions';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
@ -23,7 +23,7 @@ const StyledDoubleTextContainer = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledTextInput = styled(StyledInput)`
|
||||
const StyledTextInput = styled(UIStyledTextInput)`
|
||||
margin: 0 ${({ theme }) => theme.spacing(0.5)};
|
||||
padding: 0;
|
||||
width: ${({ width }) => (width ? `${width}px` : 'auto')};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
@ -10,6 +10,7 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
|
||||
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
|
||||
|
||||
@ -43,6 +44,7 @@ const StyledControlContainer = styled.div<{ disabled?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ disabled, theme }) =>
|
||||
disabled ? theme.font.color.tertiary : theme.font.color.primary};
|
||||
@ -88,6 +90,8 @@ export const Select = <Value extends string | number | null>({
|
||||
value,
|
||||
withSearchInput,
|
||||
}: SelectProps<Value>) => {
|
||||
const selectContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const theme = useTheme();
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
|
||||
@ -109,6 +113,15 @@ export const Select = <Value extends string | number | null>({
|
||||
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
const { useListenClickOutside } = useClickOutsideListener(dropdownId);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [selectContainerRef],
|
||||
callback: () => {
|
||||
closeDropdown();
|
||||
},
|
||||
});
|
||||
|
||||
const selectControl = (
|
||||
<StyledControlContainer disabled={isDisabled}>
|
||||
<StyledControlLabel>
|
||||
@ -133,6 +146,7 @@ export const Select = <Value extends string | number | null>({
|
||||
fullWidth={fullWidth}
|
||||
tabIndex={0}
|
||||
onBlur={onBlur}
|
||||
ref={selectContainerRef}
|
||||
>
|
||||
{!!label && <StyledLabel>{label}</StyledLabel>}
|
||||
{isDisabled ? (
|
||||
|
||||
@ -20,20 +20,6 @@ import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
|
||||
export type TextInputComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
disableHotkeys?: boolean;
|
||||
error?: string;
|
||||
RightIcon?: IconComponent;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
@ -110,6 +96,21 @@ const StyledTrailingIcon = styled.div`
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
disableHotkeys?: boolean;
|
||||
error?: string;
|
||||
RightIcon?: IconComponent;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: () => void;
|
||||
};
|
||||
|
||||
const TextInputComponent = (
|
||||
{
|
||||
className,
|
||||
@ -163,6 +164,7 @@ const TextInputComponent = (
|
||||
inputRef.current?.blur();
|
||||
},
|
||||
InputHotkeyScope.TextInput,
|
||||
{ enabled: !disableHotkeys },
|
||||
);
|
||||
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { IconComponentProps } from '@/ui/display/icon/types/IconComponent';
|
||||
import { SELECT_COUNTRY_DROPDOWN_ID } from '@/ui/input/components/internal/country/constants/SelectCountryDropdownId';
|
||||
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
|
||||
export const CountrySelect = ({
|
||||
selectedCountryName,
|
||||
onChange,
|
||||
}: {
|
||||
selectedCountryName: string;
|
||||
onChange: (countryCode: string) => void;
|
||||
}) => {
|
||||
const countries = useCountries();
|
||||
|
||||
const options: SelectOption<string>[] = useMemo(() => {
|
||||
return countries.map<SelectOption<string>>(({ countryName, Flag }) => ({
|
||||
label: countryName,
|
||||
value: countryName,
|
||||
Icon: (props: IconComponentProps) =>
|
||||
Flag({ width: props.size, height: props.size }), // TODO : improve this ?
|
||||
}));
|
||||
}, [countries]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
fullWidth
|
||||
dropdownId={SELECT_COUNTRY_DROPDOWN_ID}
|
||||
options={options}
|
||||
label="COUNTRY"
|
||||
withSearchInput
|
||||
onChange={onChange}
|
||||
value={selectedCountryName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const SELECT_COUNTRY_DROPDOWN_ID = 'select-country-picker';
|
||||
@ -0,0 +1,37 @@
|
||||
import { useMemo } from 'react';
|
||||
import { hasFlag } from 'country-flag-icons';
|
||||
import * as Flags from 'country-flag-icons/react/3x2';
|
||||
import { getCountries, getCountryCallingCode } from 'libphonenumber-js';
|
||||
|
||||
import { Country } from '@/ui/input/components/internal/types/Country';
|
||||
|
||||
export const useCountries = () => {
|
||||
return useMemo<Country[]>(() => {
|
||||
const regionNamesInEnglish = new Intl.DisplayNames(['en'], {
|
||||
type: 'region',
|
||||
});
|
||||
|
||||
const countryCodes = getCountries();
|
||||
|
||||
return countryCodes.reduce<Country[]>((result, countryCode) => {
|
||||
const countryName = regionNamesInEnglish.of(countryCode);
|
||||
|
||||
if (!countryName) return result;
|
||||
|
||||
if (!hasFlag(countryCode)) return result;
|
||||
|
||||
const Flag = Flags[countryCode];
|
||||
|
||||
const callingCode = getCountryCallingCode(countryCode);
|
||||
|
||||
result.push({
|
||||
countryCode,
|
||||
countryName,
|
||||
callingCode,
|
||||
Flag,
|
||||
});
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
}, []);
|
||||
};
|
||||
@ -1,19 +1,17 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getCountries, getCountryCallingCode } from 'react-phone-number-input';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { hasFlag } from 'country-flag-icons';
|
||||
import * as Flags from 'country-flag-icons/react/3x2';
|
||||
import { CountryCallingCode } from 'libphonenumber-js';
|
||||
|
||||
import { IconChevronDown, IconWorld } from '@/ui/display/icon';
|
||||
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||
import { Country } from '@/ui/input/components/internal/types/Country';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
|
||||
|
||||
import { CountryPickerDropdownSelect } from './CountryPickerDropdownSelect';
|
||||
import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect';
|
||||
|
||||
import 'react-phone-number-input/style.css';
|
||||
|
||||
@ -57,14 +55,7 @@ const StyledIconContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export type Country = {
|
||||
countryCode: string;
|
||||
countryName: string;
|
||||
callingCode: CountryCallingCode;
|
||||
Flag: Flags.FlagComponent;
|
||||
};
|
||||
|
||||
export const CountryPickerDropdownButton = ({
|
||||
export const PhoneCountryPickerDropdownButton = ({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
@ -82,34 +73,7 @@ export const CountryPickerDropdownButton = ({
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const countries = useMemo<Country[]>(() => {
|
||||
const regionNamesInEnglish = new Intl.DisplayNames(['en'], {
|
||||
type: 'region',
|
||||
});
|
||||
|
||||
const countryCodes = getCountries();
|
||||
|
||||
return countryCodes.reduce<Country[]>((result, countryCode) => {
|
||||
const countryName = regionNamesInEnglish.of(countryCode);
|
||||
|
||||
if (!countryName) return result;
|
||||
|
||||
if (!hasFlag(countryCode)) return result;
|
||||
|
||||
const Flag = Flags[countryCode];
|
||||
|
||||
const callingCode = getCountryCallingCode(countryCode);
|
||||
|
||||
result.push({
|
||||
countryCode,
|
||||
countryName,
|
||||
callingCode,
|
||||
Flag,
|
||||
});
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
}, []);
|
||||
const countries = useCountries();
|
||||
|
||||
useEffect(() => {
|
||||
const country = countries.find(({ countryCode }) => countryCode === value);
|
||||
@ -132,7 +96,7 @@ export const CountryPickerDropdownButton = ({
|
||||
</StyledDropdownButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<CountryPickerDropdownSelect
|
||||
<PhoneCountryPickerDropdownSelect
|
||||
countries={countries}
|
||||
selectedCountry={selectedCountry}
|
||||
onChange={handleChange}
|
||||
@ -1,6 +1,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Country } from '@/ui/input/components/internal/types/Country';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@ -8,8 +9,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
|
||||
|
||||
import { Country } from './CountryPickerDropdownButton';
|
||||
|
||||
import 'react-phone-number-input/style.css';
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
@ -27,7 +26,7 @@ const StyledIconContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const CountryPickerDropdownSelect = ({
|
||||
export const PhoneCountryPickerDropdownSelect = ({
|
||||
countries,
|
||||
selectedCountry,
|
||||
onChange,
|
||||
@ -0,0 +1,9 @@
|
||||
import * as Flags from 'country-flag-icons/react/3x2';
|
||||
import { CountryCallingCode } from 'libphonenumber-js';
|
||||
|
||||
export type Country = {
|
||||
countryCode: string;
|
||||
countryName: string;
|
||||
callingCode: CountryCallingCode;
|
||||
Flag: Flags.FlagComponent;
|
||||
};
|
||||
Reference in New Issue
Block a user