0.9.6 • Published 1 year ago

react-bella-email v0.9.6

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

:love_letter: React Bella Email

The email input field that your users will love.

Demo and examplesStackblitz

react-bella-email

React Bella Email is a tiny, zero-dependency controlled component that aims to replace the typical <input type="email" /> of your form by providing the best UX with all the flexibility you'd expect from a native input:

  • Fully accessible with great keyboard controls
  • Completely unstyled and white labeled (ships with zero CSS)
  • Forward most common event handlers and attributes
  • Controllable with React Hook Form

:bulb: React Bella Email also ships with a curated list of ~160 world's most popular email providers in order to get started quickly (thanks to @mailcheck).

:floppy_disk: Installation

npm i -S react-bella-email
# yarn add react-bella-email
# pnpm add react-bella-email

:cyclone: Props

PropDescriptionTypeDefaultRequired
valueState or portion of state to hold the emailstringundefined:white_check_mark:
onChangeState setter or custom dispatcher to update the emailOnChangeundefined:white_check_mark:
baseListDomains to suggest while typing the usernamestring[]undefined:white_check_mark:
refineListDomains to refine suggestions after typing @string[][]:x:
onSelectCustom callback on suggestion selectOnSelect() => {}:x:
minCharsMinimum chars required to display suggestions1 | 2 | 3 | 4 | 5 | 6 | 7 | 82:x:
maxResultsMaximum number of suggestions to display2 | 3 | 4 | 5 | 6 | 7 | 86:x:
classNamesClass names for each elementClassNamesundefined:x:
classNameClass name of the wrapper elementstringundefined:x:
wrapperIdDOM ID of the wrapper elementstringundefined:x:
customPrefixCustom prefix for dropdown unique IDstringrbe_:x:
isInvalidValue of aria-invalidbooleanundefined:x:

Are also available:

  • Events: onBlur, onFocus, onInput and onKeyDown.

  • Attributes: id, name, placeholder, required, disabled, readOnly and pattern.

  • React's ref.

:bulb: They are all forwarded to the <input /> element.

:art: Styling

The component renders a single div with a very simple structure:

Wrapper — div
├── Email Input Field — input
└── Dropdown — ul
    └── Suggestions - li[]
        └──[username - span:first-of-type] [@domain.com - span:last-of-type]

You can either specify classNames for any element you'd like to style:

const myClassNames = {
  wrapper: 'my-wrapper',
  input: 'my-input',
  dropdown: 'my-dropdown',
  suggestion: 'my-suggestion',
  username: 'my-username',
  domain: 'my-domain'
};

function App() {
  const [email, setEmail] = useState('');

  return (
    <Email
      classNames={myClassNames}
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  );
}
import type { ClassNames } from 'react-bella-email';

const myClassNames: ClassNames = {
  wrapper: 'my-wrapper',
  input: 'my-input'
};

You can add a this property in VSCode's settings.json in order to enable autcomplete for any object property or variable ending with ClassNames.

  "tailwindCSS.experimental.classRegex": [
    ["ClassNames \\=([^;]*);", "'([^']*)'"],
    ["ClassNames \\=([^;]*);", "\"([^\"]*)\""],
    ["ClassNames \\=([^;]*);", "\\`([^\\`]*)\\`"]
  ],

Or add a class to the wrapper div via className prop, and target any child:

.my-wrapper {
  /* Wrapper */
}

.my-wrapper input {
  /* Input field */
}

.my-wrapper li > span:first-of-type {
  /* Username */
}

/* ... */

This package ships with zero css. Initial styles enough to see the component in action may match the following properties:

.my-wrapper,
.my-input {
  position: relative;
}

.my-input,
.my-dropdown,
.my-suggestion {
  font-size: inherit;
  box-sizing: border-box;
  width: 100%;
}

.my-dropdown {
  position: absolute;
  margin: 0.45rem 0 0 0;
  padding: 0;
  list-style-type: none;
  z-index: 999;
}

.my-suggestion {
  cursor: pointer;
  user-select: none;
  overflow: hidden;
}

Focus/Hover styles

Although you can target the pseudo classes :hover and :focus, it is recommended instead to target the attribute data-active-email in order to avoid :hover styles to be applied to a suggestion as soon as the dropdown is opened (in case the cursor was hovering it).

.my-suggestion[data-active-email='true'] {
  background-color: aliceblue;
}

.my-suggestion:focus {
  outline: none;
}

:dna: Modes

1. Basic Mode

Once users start typing, it displays a list of base suggestions and hides it once they type @ . It already gives a nice UX and should be enough for the vast majority of websites:

Before typing @After typing @
react-bella-emailreact-bella-email
import { Email } from 'react-bella-email';

const baseList = [
  'gmail.com',
  'yahoo.com',
  'hotmail.com',
  'aol.com',
  'msn.com',
  'proton.me'
];

function App() {
  const [email, setEmail] = useState('');

  return (
    <Email
      className="my-wrapper"
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  );
}

2. Refine Mode

Acts like Basic Mode until users type @ . Then as they start typing the domain, it starts refining suggestions according to an extended list of domains.

Before typing @After typing @
react-bella-emailreact-bella-email

All you have to do is to provide a second array of domains to refineList prop. This package ships with a curated list of the ~160 most popular world domains that you can directly import and use (thanks to @mailcheck):

import { Email, domains } from 'react-bella-email';

const baseList = [
  'gmail.com',
  'yahoo.com',
  'hotmail.com',
  'aol.com',
  'msn.com',
  'proton.me'
];

function App() {
  const [email, setEmail] = useState('');

  return (
    <Email
      className="my-wrapper"
      baseList={baseList}
      refineList={domains}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  );
}

Alternatively, you can create your own array of domains or search for the one that more suits your audience.

:globe_with_meridians: Localization

It is great to display different suggestions according to user's browser locale. React Bella Email includes a very simple hook to do exactly that.

1 - Create an object and define lists for each browser locale:

export const lists = {
  default: ['gmail.com', 'yahoo.com', 'hotmail.com', 'aol.com', 'msn.com', 'proton.me'], // Required
  it: ['gmail.com', 'yahoo.com', 'yahoo.it', 'tiscali.it', 'libero.it', 'outlook.com'],
  'it-CH': ['gmail.com', 'outlook.com', 'bluewin.ch', 'gmx.de', 'libero.it', 'sunrise.ch']
};

:warning: Make sure to define the object outside of your component, otherwise it will be recreated on every render causing an infinite loop.

import type { LocalizedList } from 'react-bella-email';

export const lists: LocalizedList = {
  default: ['gmail.com', 'yahoo.com', 'hotmail.com', 'aol.com', 'msn.com', 'proton.me'], // Required
  it: ['gmail.com', 'yahoo.com', 'yahoo.it', 'tiscali.it', 'libero.it', 'outlook.com']
};

You can define lang codes with or without country codes.

If you define a language without country code (such as it), by default it will match browser locales such as it, it-CH, it-IT and so on.

If you define it-CH it will match it-CH but not it or it-IT.

If you define both it-CH and it, it-CH will match only it-CH and it will match it, it-IT and so on.

2 - Use the hook:

import { lists } from './lists';
import { Email, useLocalizedList } from 'react-bella-email';

function App() {
  const baseList = useLocalizedList(lists);
  const [email, setEmail] = useState('');

  return (
    <Email
      className="my-wrapper"
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  );
}

Usage with internationalization frameworks

If you prefer to keep the suggestions in line with your app locale instead of the browser's one, you can directly pass the locale string as second argument:

import lists from './lists';
import { useRouter } from 'next/router';
import { Email, useLocalizedList } from 'react-bella-email';

function App() {
  const { locale } = useRouter();
  const baseList = useLocalizedList(lists, locale);

  const [email, setEmail] = useState('');

  return (
    <Email
      className="my-wrapper"
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  );
}

:8ball: onSelect callback

If you need to invoke a callback everytime a suggestion is selected (either with mouse or keyboard), you can do that by passing a function to onSelect prop:

import { Email, domains } from 'react-bella-email';

function App() {
  const [email, setEmail] = useState('');

  function handleSelect({ value, keyboard, position }) {
    console.log(value, keyboard, position);
  }

  return (
    <Email
      className="my-wrapper"
      baseList={baseList}
      refineList={domains}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      onSelect={handleSelect}
      value={email}
    />
  );
}
type OnSelectData = {
  value: string;
  keyboard: boolean;
  position: number;
};

type OnSelect = (object: OnSelectData) => void | Promise<void>;

React Hook Form

No special configuration needed, it just works. Just follow the official React Hook Form's Controller documentation.

:keyboard: Keyboard controls

  • ↑ ↓ - Navigate through suggestions / input
  • ← → - Move cursor and focus the input field while keeping list open
  • Backspace / Alphanumeric keys - Edit the input value and keep refining suggestions
  • Enter / Space - Confirm the suggestion
  • Escape - Close the list and focus the input field
  • Tab / Shift + Tab - Close the list and go to next/prev focusable input

:dvd: License

MIT Licensed. Copyright (c) Simone Mastromattei 2022.