sumup-oss / circuit-ui

SumUp's design system for the web
https://circuit.sumup.com
Apache License 2.0
923 stars 130 forks source link

Proposal: International phone input with country select #860

Closed larimaza closed 4 months ago

larimaza commented 3 years ago

As we scale the internationalization of our various platforms at SumUp, it becomes clear that we have a need for international phone inputs to enable qualified leads and/or more user profile info upon sign up.

One example already in use is the marketing website. The first version was a plain text field, but since they got many invalid phone numbers, the country code selector adds a more robust user guidance.

The same need came up in the Partner Portal sign up as we implement internationalization. So I started to implement a similar component, fitting a Circuit Select component to the left of a regular Circuit Input, like the image below. But since the Select component uses the native browser dropdown, it wasn't possible to create a nice experience adding the country flag and name - the result was a dropdown with 240 numbers, and as you know... Our users deserve better!

Screen Shot 2021-03-26 at 11 39 01

Time was running out, and due to the non-trivial added complexity of making this look more like the marketing website input, we decided to go for a third party library instead, which is react-phone-input-2. This link has a demo to their custom dropdown which is nicely searchable, includes a flag and country name, and even variable masks. It was styled to look like Circuit UI (excuse the button focus style for now, it's very tricky to style that):

Screen Shot 2021-03-26 at 11 45 44

This is already in production and you can test the behavior here. It has no validations for now because of the added complexity of validating various formats.

The proposal is to create our own international phone input that works somewhat like that library.

Visual

This is what my prototype looked like:

Screen Shot 2021-03-26 at 11 19 52

I used the renderPrefix prop in the Select component to pass the current flag icon (further detail in the end of this issue).

The main difference from the library is that my prototype had the country code in the Select component, while the library writes the current country code into the Input value since it first loads.

The experience is slightly different since the latter enables the user to delete/edit the country code number without having to interact with the dropdown. I'd like to have an opinion from a designer on which seems best.

Both options have to control the state of the component in a way that the value is only set when there is a phone number, otherwise we might submit something like +55 as a phone number when the field is optional and not interacted with.

Context

This component can be used in any sign up and user profile forms which include a phone number.

State

The states would be normal, focused, hovered, error, valid and invalid (like any input). There's also the open state for the dropdown, and we'll definitely have to create a custom dropdown instead of using the native browser option.

Assuming the dropdown will be searchable, there is also returning the search (or an empty state for when there are no matches found).

Progressive enhancement

We could lazyload the options in the dropdown to make for a faster experience.

Important notes

import React from 'react';
import { FlagBr } from '@sumup/icons';

export const COUNTRY_OPTIONS = [
  {
    name: 'Brazil',
    label: '+55',
    value: '55',
    isocode: 'BR',
    flagcomponent: <FlagBr />,
  },
]
long-lazuli commented 2 years ago

The list with all country options could be provided by Circuit, but we should be able to localize it to translate country names according to context. We need to refine this.

About that, we recently start using Intl.displayNames and it's really effective; Looks like it's exactly what you seem to look for.

We did make a hook for that.


const useIntlDisplayNames = (
  options: Intl.DisplayNamesOptions = { type: 'region' },
  providedLocales?: Intl.LocalesArgument,
) => {
  const { i18n } = useTranslation();
  const locales = providedLocales ?? [i18n.language] ?? [...global.navigator.languages];
  const kindOfDict = new Intl.DisplayNames(locales, options);

  return (strToTranslate?: string) => {
    if (!strToTranslate) return '';

    if (!kindOfDict) {
      console.warn('Intl.DisplayNames not found');

      return strToTranslate;
    }

    const displayName = kindOfDict.of(strToTranslate);
    if (!displayName) {
      console.warn(`I don't know how to translate ${strToTranslate}`);

      return strToTranslate;
    }

    return displayName;
  };
};

export default {
  displayNames: useIntlDisplayNames,
};

and we can now use it like we would with a translation utility.

  // `bt` is for "browserTranslate" here.
  const bt = useIntl.displayNames();
  return (
    <div>{bt(countryCode)}</div>
  )
connor-baer commented 2 years ago

Intl.DisplayNames does not meet SumUp's (internal link) and Circuit UI's browser support policy.

connor-baer commented 4 months ago

We've released an experimental PhoneNumberInput component in Circuit UI v8.9 🎉

Thanks for the contribution @roma-claudio!