all repos — caroster @ 7776ccab27313a6d0a357c130a8367c2ee00c784

[Octree] Group carpool to your event https://caroster.io

frontend/components/PhoneInput/index.tsx (view raw)

  1import React, {useState} from 'react';
  2import {PhoneNumberUtil} from 'google-libphonenumber';
  3import {
  4  InputAdornment,
  5  MenuItem,
  6  MenuProps,
  7  Select,
  8  TextField,
  9  TextFieldProps,
 10  Typography,
 11} from '@mui/material';
 12import {
 13  CountryIso2,
 14  defaultCountries,
 15  FlagImage,
 16  parseCountry,
 17  usePhoneInput,
 18} from 'react-international-phone';
 19import 'react-international-phone/style.css';
 20
 21interface Props {
 22  value: string;
 23  required?: boolean;
 24  onChange: ({
 25    phone,
 26    country,
 27  }: {
 28    phone: string;
 29    country: CountryIso2 | '';
 30    error: boolean;
 31  }) => void;
 32  label: string;
 33}
 34
 35const PhoneInput = ({
 36  value,
 37  onChange,
 38  label,
 39  required,
 40  ...textFieldProps
 41}: Omit<TextFieldProps, 'onChange'> & Props) => {
 42  const [phone, setPhone] = useState(value);
 43
 44  const browserLocales = navigator.language.split('-');
 45  const defaultCountry =
 46    browserLocales[browserLocales.length - 1].toLowerCase();
 47
 48  const {inputValue, handlePhoneValueChange, inputRef, country, setCountry} =
 49    usePhoneInput({
 50      defaultCountry: defaultCountry || defaultCountries[0][1],
 51      value: phone,
 52      countries: defaultCountries,
 53      onChange: ({phone, country}) => {
 54        const formatedPhone = phone?.replace(/^\+0*/, '+');
 55        setPhone(formatedPhone);
 56        if (isPhoneValid(formatedPhone))
 57          onChange({phone: formatedPhone, country: country.iso2, error: false});
 58        else onChange({phone: '', country: '', error: true});
 59      },
 60    });
 61
 62  return (
 63    <TextField
 64      fullWidth
 65      required={required}
 66      error={inputValue && (!phone || value !== phone)}
 67      {...textFieldProps}
 68      label={label}
 69      value={inputValue}
 70      onChange={handlePhoneValueChange}
 71      type="tel"
 72      inputRef={inputRef}
 73      slotProps={{
 74        input: {
 75          startAdornment: (
 76            <InputAdornment position="start" sx={{mr: 0.5, ml: -2}}>
 77              <Select
 78                MenuProps={menuProps}
 79                sx={selectSx}
 80                value={country.iso2}
 81                onChange={e => setCountry(e.target.value)}
 82                renderValue={value => (
 83                  <FlagImage iso2={value} style={{display: 'flex'}} />
 84                )}
 85              >
 86                {defaultCountries.map(c => {
 87                  const country = parseCountry(c);
 88                  return (
 89                    <MenuItem key={country.iso2} value={country.iso2}>
 90                      <FlagImage
 91                        iso2={country.iso2}
 92                        style={{marginRight: '8px'}}
 93                      />
 94                      <Typography marginRight="8px">{country.name}</Typography>
 95                      <Typography color="gray">+{country.dialCode}</Typography>
 96                    </MenuItem>
 97                  );
 98                })}
 99              </Select>
100            </InputAdornment>
101          ),
102        },
103      }}
104    />
105  );
106};
107
108const phoneUtil = PhoneNumberUtil.getInstance();
109const isPhoneValid = (phone: string) => {
110  try {
111    return phoneUtil.isValidNumber(phoneUtil.parseAndKeepRawInput(phone));
112  } catch (error) {
113    return false;
114  }
115};
116
117const selectSx = {
118  width: 'max-content',
119  // Remove default outline (display only on focus)
120  fieldset: {
121    display: 'none',
122  },
123  '&.Mui-focused:has(div[aria-expanded="false"])': {
124    fieldset: {
125      display: 'block',
126    },
127  },
128  // Update default spacing
129  '.MuiSelect-select': {
130    padding: '8px',
131    paddingRight: '24px !important',
132  },
133  svg: {
134    right: 0,
135  },
136};
137
138const menuProps: Partial<MenuProps> = {
139  style: {
140    height: '300px',
141    width: '360px',
142    top: '10px',
143    left: '-34px',
144  },
145  transformOrigin: {
146    vertical: 'top',
147    horizontal: 'left',
148  },
149};
150
151export default PhoneInput;