Skip to content

Commit 4b4c619

Browse files
committed
feat: add PhoneInput and FlagImage components with associated types and utility functions
1 parent 5371d11 commit 4b4c619

File tree

15 files changed

+188
-35
lines changed

15 files changed

+188
-35
lines changed

src/Shared/Components/CountrySelect/CountrySelect.component.tsx

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,62 @@
1-
import { SelectPicker } from '../SelectPicker'
2-
import { COUNTRY_OPTIONS } from './constants'
1+
import { useMemo } from 'react'
2+
import { SelectPicker, SelectPickerProps } from '../SelectPicker'
33
import { CountrySelectProps } from './types'
4+
import { getCountryOptions } from './utils'
5+
import { FlagImage } from '../FlagImage'
46

5-
const CountrySelect = ({ selectedCountry, label, required, error, handleChange, placeholder }: CountrySelectProps) => {
6-
const onChange = (option: (typeof COUNTRY_OPTIONS)[number]) => {
7-
handleChange(option.value)
7+
const CountrySelect = ({
8+
selectedCountry,
9+
label,
10+
required,
11+
error,
12+
handleChange,
13+
placeholder,
14+
name,
15+
variant = 'default',
16+
size,
17+
}: CountrySelectProps) => {
18+
const countryOptions = useMemo(() => getCountryOptions(variant), [variant])
19+
20+
const onChange = (option: (typeof countryOptions)[number]) => {
21+
handleChange(option.value.iso2)
822
}
923

10-
const selectedValue: (typeof COUNTRY_OPTIONS)[number] = selectedCountry
11-
? COUNTRY_OPTIONS.find((option) => option.value === selectedCountry) || {
12-
value: 'XX',
13-
label: 'Unknown',
14-
startIcon: null,
15-
}
24+
const selectedOption: (typeof countryOptions)[number] = selectedCountry
25+
? countryOptions.find((option) => option.value.iso2 === selectedCountry) || null
1626
: null
1727

28+
const filterOption: SelectPickerProps<(typeof countryOptions)[number]['value']>['filterOption'] = (
29+
option,
30+
searchText,
31+
) => {
32+
const searchValue = searchText.toLowerCase()
33+
const searchCriterion = [option.data.value.name.toLowerCase(), `+${option.data.value.dialCode.toLowerCase()}`]
34+
35+
return searchCriterion.some((criterion) => criterion.includes(searchValue))
36+
}
37+
38+
const getOptionValue: SelectPickerProps<(typeof countryOptions)[number]['value']>['getOptionValue'] = (option) =>
39+
option.value.iso2
40+
1841
return (
19-
<SelectPicker
20-
inputId="select-picker__country-select"
21-
value={selectedValue}
42+
<SelectPicker<(typeof countryOptions)[number]['value'], false>
43+
inputId={`select-picker__country-select--${name}`}
44+
value={selectedOption}
2245
onChange={onChange}
2346
error={error}
2447
required={required}
2548
label={label}
26-
options={COUNTRY_OPTIONS}
49+
options={countryOptions}
2750
placeholder={placeholder}
51+
size={size}
52+
filterOption={filterOption}
53+
getOptionValue={getOptionValue}
54+
icon={
55+
variant === 'selectPhoneCode' && selectedOption?.value?.iso2 ? (
56+
<FlagImage country={selectedOption?.value?.iso2} />
57+
) : null
58+
}
59+
controlShouldRenderValue={variant !== 'selectPhoneCode'}
2860
/>
2961
)
3062
}

src/Shared/Components/CountrySelect/constants.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export { default as CountrySelect } from './CountrySelect.component'
2-
export { type CountryISO2Type } from './types'
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ParsedCountry } from 'react-international-phone'
1+
import { CountryISO2Type } from '@Shared/index'
22
import { SelectPickerProps } from '../SelectPicker'
33

4-
export type CountryISO2Type = ParsedCountry['iso2']
5-
6-
export interface CountrySelectProps extends Pick<SelectPickerProps, 'required' | 'label' | 'error' | 'placeholder'> {
4+
export interface CountrySelectProps
5+
extends Pick<SelectPickerProps, 'required' | 'label' | 'error' | 'placeholder' | 'size'>,
6+
Required<Pick<SelectPickerProps, 'name'>> {
77
selectedCountry: CountryISO2Type
88
handleChange: (iso2: CountryISO2Type) => void
9+
variant?: 'default' | 'selectPhoneCode'
910
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { defaultCountries, parseCountry, ParsedCountry } from 'react-international-phone'
2+
import { CountrySelectProps } from './types'
3+
import { SelectPickerOptionType } from '../SelectPicker'
4+
import { FlagImage } from '../FlagImage'
5+
6+
export const getCountryOptions = (variant: CountrySelectProps['variant']): SelectPickerOptionType<ParsedCountry>[] =>
7+
defaultCountries.map((countryData) => {
8+
const parsedCountry = parseCountry(countryData)
9+
10+
const countryWithDialCode = `${parsedCountry.name} (+${parsedCountry.dialCode})`
11+
12+
return {
13+
value: parsedCountry,
14+
label:
15+
variant === 'selectPhoneCode' ? (
16+
<div className="flexbox dc__gap-8 dc__align-items-center">
17+
<FlagImage country={parsedCountry.iso2} size={16} />
18+
<span className="fs-13 fw-4 lh-20">{countryWithDialCode}</span>
19+
</div>
20+
) : (
21+
parsedCountry.name
22+
),
23+
startIcon: variant === 'default' ? <FlagImage country={parsedCountry.iso2} /> : null,
24+
}
25+
})

src/Shared/Components/CustomInput/CustomInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ const CustomInput = ({
4747
endIconButtonConfig,
4848
labelTippyCustomizedConfig,
4949
labelTooltipConfig,
50+
inputRef: inputRefProp,
5051
...props
5152
}: CustomInputProps) => {
52-
const inputRef = useRef<HTMLInputElement>(null)
53+
const localInputRef = useRef<HTMLInputElement>(null)
54+
const inputRef = inputRefProp || localInputRef
5355

5456
useEffect(() => {
5557
setTimeout(() => {

src/Shared/Components/CustomInput/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { InputHTMLAttributes } from 'react'
17+
import { InputHTMLAttributes, MutableRefObject } from 'react'
1818
import { ComponentSizeType } from '@Shared/constants'
1919
import { FormFieldWrapperProps } from '../FormFieldWrapper'
2020
import { ButtonComponentType, ButtonProps } from '../Button'
@@ -48,6 +48,10 @@ export interface CustomInputProps
4848
*/
4949
endIconButtonConfig?: Required<Pick<ButtonProps<ButtonComponentType.button>, 'icon' | 'onClick' | 'ariaLabel'>> &
5050
Pick<ButtonProps<ButtonComponentType.button>, 'disabled' | 'showAriaLabelInTippy' | 'style'>
51+
/**
52+
* Ref for the input element
53+
*/
54+
inputRef?: MutableRefObject<HTMLInputElement>
5155
}
5256

5357
export interface PasswordFieldProps extends Omit<CustomInputProps, 'endIconButtonConfig' | 'type'> {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { FlagImage as FlagImageHelper } from 'react-international-phone'
2+
import { FlagImageProps } from './types'
3+
4+
const FlagImage = ({ country, size }: FlagImageProps) => (
5+
<FlagImageHelper
6+
iso2={country}
7+
protocol={window.isSecureContext ? 'https' : 'http'}
8+
{...(size ? { size: `${size}px` } : {})}
9+
/>
10+
)
11+
12+
export default FlagImage
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as FlagImage } from './FlagImage.component'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { CountryISO2Type } from '@Shared/index'
2+
3+
export interface FlagImageProps {
4+
country: CountryISO2Type
5+
size?: number
6+
}

0 commit comments

Comments
 (0)