all repos — caroster @ e3dd820256e0c4ea2c317a33c8fc29f9a83f3711

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

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

  1import React, {useState} from 'react';
  2import {
  3  InputAdornment,
  4  MenuItem,
  5  Select,
  6  TextField,
  7  TextFieldProps,
  8  Typography,
  9} from '@mui/material';
 10import {
 11  CountryIso2,
 12  defaultCountries,
 13  FlagImage,
 14  getActiveFormattingMask,
 15  parseCountry,
 16  usePhoneInput,
 17} from 'react-international-phone';
 18
 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        setPhone(phone);
 55        const mask = getActiveFormattingMask({
 56          phone: phone,
 57          country: country,
 58        });
 59        const digitnumbers = mask.split('').filter(c => c === '.').length;
 60        const isValid =
 61          phone.length === 1 + country.dialCode.length + digitnumbers;
 62        if (isValid) {
 63          onChange({phone, country: country.iso2, error: false});
 64        } else {
 65          onChange({phone: '', country: '', error: true});
 66        }
 67      },
 68    });
 69
 70  return (
 71    <TextField
 72      fullWidth
 73      required={required}
 74      error={inputValue && (!phone || value !== phone)}
 75      {...textFieldProps}
 76      label={label}
 77      value={inputValue}
 78      onChange={handlePhoneValueChange}
 79      type="tel"
 80      inputRef={inputRef}
 81      InputProps={{
 82        startAdornment: (
 83          <InputAdornment
 84            position="start"
 85            style={{marginRight: '2px', marginLeft: '-8px'}}
 86          >
 87            <Select
 88              MenuProps={{
 89                style: {
 90                  height: '300px',
 91                  width: '360px',
 92                  top: '10px',
 93                  left: '-34px',
 94                },
 95                transformOrigin: {
 96                  vertical: 'top',
 97                  horizontal: 'left',
 98                },
 99              }}
100              sx={{
101                width: 'max-content',
102                // Remove default outline (display only on focus)
103                fieldset: {
104                  display: 'none',
105                },
106                '&.Mui-focused:has(div[aria-expanded="false"])': {
107                  fieldset: {
108                    display: 'block',
109                  },
110                },
111                // Update default spacing
112                '.MuiSelect-select': {
113                  padding: '8px',
114                  paddingRight: '24px !important',
115                },
116                svg: {
117                  right: 0,
118                },
119              }}
120              value={country.iso2}
121              onChange={e => setCountry(e.target.value)}
122              renderValue={value => (
123                <FlagImage iso2={value} style={{display: 'flex'}} />
124              )}
125            >
126              {defaultCountries.map(c => {
127                const country = parseCountry(c);
128                return (
129                  <MenuItem key={country.iso2} value={country.iso2}>
130                    <FlagImage
131                      iso2={country.iso2}
132                      style={{marginRight: '8px'}}
133                    />
134                    <Typography marginRight="8px">{country.name}</Typography>
135                    <Typography color="gray">+{country.dialCode}</Typography>
136                  </MenuItem>
137                );
138              })}
139            </Select>
140          </InputAdornment>
141        ),
142      }}
143    />
144  );
145};
146
147export default PhoneInput;