Code Monkey home page Code Monkey logo

react-email-autocomplete's Introduction

React Email Autocomplete

npm GitHub Workflow Status dependency-count

Before typing @ After typing @ (optional)
@smastrom/react-email-autocomplete @smastrom/react-email-autocomplete

React Email Autocomplete is an unstyled, zero-dependency component inspired by some european flight booking websites. As soon as users start typing their email address, it will suggest the most common email providers.

  • Completely unstyled and white labeled (ships with zero CSS)
  • Fully accessible with superlative keyboard controls
  • Forward any event and attribute to the <input /> element or control it with React Hook Form

Demo and examples โ€” Stackblitz โ€” NextJS


๐Ÿ’พ Installation

pnpm add @smastrom/react-email-autocomplete
# npm i @smastrom/react-email-autocomplete
# yarn add @smastrom/react-email-autocomplete

๐ŸŽจ Usage / 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]

Specify classNames for each element you'd like to style:

import { Email } from '@smastrom/react-email-autocomplete'

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

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

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

  return (
    <Email
      classNames={classNames}
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}
NextJS App Router

components/Email.tsx

'use client'

import { useState } from 'react'
import { Email as EmailAutocomplete } from '@smastrom/react-email-autocomplete'

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

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

export function Email() {
  const [email, setEmail] = useState('')

  return (
    <EmailAutocomplete
      classNames={classNames}
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}

app/page.tsx

import { Email } from '@/components/Email'

export default function Home() {
  return (
    <main>
      {/* ... */}
      <Email />
      {/* ... */}
    </main>
  )
}
TypeScript
import type { ClassNames } from '@smastrom/react-email-autocomplete'

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

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 \\=([^;]*);", "\\`([^\\`]*)\\`"]
  ],
Basic styles

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 is hovering it).

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

.my-suggestion:hover,
.my-suggestion:focus,
.my-suggestion:focus-visible {
  outline: none;
}

The attribute name can also be customized via activeDataAttr prop:

<Email
  activeDataAttr="data-custom-attr"
  classNames={{
    ...classNames,
    suggestion: 'my-suggestion',
  }}
  baseList={baseList}
  value={email}
/>
.my-suggestion[data-custom-attr='true'] {
  background-color: aliceblue;
}

๐Ÿงฌ 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 @
@smastrom/react-email-autocomplete @smastrom/react-email-autocomplete
import { Email } from '@smastrom/react-email-autocomplete'

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

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

  return (
    <Email
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}

2. Refine Mode (optional)

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 @
@smastrom/react-email-autocomplete @smastrom/react-email-autocomplete

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 '@smastrom/react-email-autocomplete'

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

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

  return (
    <Email
      baseList={baseList}
      refineList={domains}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}

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


๐ŸŒ Localization

This package ships with an optional hook that simplifies managing different lists of domains according to the browser's locale.

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

export const emailProviders = {
  default: [
    'gmail.com',
    'yahoo.com',
    'hotmail.com',
    'aol.com',
    // ...
  ],
  it: [
    'gmail.com',
    'yahoo.com',
    'yahoo.it',
    'tiscali.it',
    // ...
  ],
  'it-CH': [
    'gmail.com',
    'outlook.com',
    'bluewin.ch',
    'gmx.de',
    // ...
  ],
}
TypeScript
import type { LocalizedList } from '@smastrom/react-email-autocomplete'

export const emailProviders: LocalizedList = {
  default: [
    'gmail.com',
    'yahoo.com',
    'hotmail.com',
    'aol.com',
    // ...
  ],
  // ...
}

You may define lang codes with or without country codes.

For languages without country code (such as it), by default it will match all browser locales beginning with it such as it, it-CH, it-IT and so on.

For languages with country code (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 { Email, useLocalizedList } from '@smastrom/react-email-autocomplete'
import { emailProviders } from '@/src/static/locales'

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

  return (
    <Email
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}

Usage with internationalization frameworks or SSR

To manually set the locale, pass its code as second argument:

import { useRouter } from 'next/router'
import { emailProviders } from '@/src/static/locales'
import { Email, useLocalizedList } from '@smastrom/react-email-autocomplete'

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

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

  return (
    <Email
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      value={email}
    />
  )
}

Or with NextJS App router:

components/Email.tsx

'use client'

import {
  Email as EmailAutocomplete,
  useLocalizedList,
} from '@smastrom/react-email-autocomplete'
import { emailProviders } from '@/static/locales'

export function Email({ lang }: { lang: string }) {
  const baseList = useLocalizedList(emailProviders, lang)
  const [email, setEmail] = useState('')

  return (
    <EmailAutocomplete
      classNames={classNames}
      baseList={baseList}
      onChange={setEmail}
      value={email}
    />
  )
}

app/page.tsx

import { Email } from '@/components/Email'
import { headers } from 'next/headers'

export default function Home() {
  const headersList = headers()
  const lang = headersList.get('accept-language')?.split(',')[0]

  return (
    <main>
      <Email lang={lang} />
    </main>
  )
}

๐ŸŽฑ onSelect callback

To invoke a callback everytime a suggestion is selected (either with mouse or keyboard), pass a callback to onSelect prop:

import { Email } from '@smastrom/react-email-autocomplete'

function handleSelect(data) {
  console.log(data) // { value: '[email protected]', keyboard: true, position: 0 }
}

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

  return (
    <Email
      baseList={baseList}
      onChange={setEmail} // or (newValue) => customSetter(newValue)
      onSelect={handleSelect}
      value={email}
    />
  )
}
Type Definition
type OnSelectData = {
  value: string
  keyboard: boolean
  position: number
}

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

๐ŸŒ€ Props

Prop Description Type Default Required
value State or portion of state that holds the email value string undefined โœ…
onChange State setter or custom dispatcher to update the email OnChange undefined โœ…
baseList Domains to suggest while typing the username string[] undefined โœ…
refineList Domains to refine suggestions after typing @ string[] [] โŒ
onSelect Custom callback on suggestion select OnSelect () => {} โŒ
minChars Minimum chars required to display suggestions 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 2 โŒ
maxResults Maximum number of suggestions to display 2 | 3 | 4 | 5 | 6 | 7 | 8 6 โŒ
classNames Class names for each element ClassNames undefined โŒ
className Class name of the root element string undefined โŒ
activeDataAttr Attribute name to set on focused/hovered suggestion string data-active-email โŒ
dropdownAriaLabel Aria label for the dropdown list string Suggestions โŒ

๐Ÿ’ก React's ref and any other HTMLInputElement attribute can be passed as prop to the component and it will be forwarded to the input element.


โŒจ๏ธ 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

React Hook Form

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


๐Ÿ“€ License

MIT

react-email-autocomplete's People

Contributors

smastrom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-email-autocomplete's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.