Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/smastrom/react-email-autocomplete
💌 Headless email input field with custom suggestions. Inspired by european flight booking websites.
https://github.com/smastrom/react-email-autocomplete
react react-autocomplete react-autosuggest react-email react-email-component
Last synced: 20 days ago
JSON representation
💌 Headless email input field with custom suggestions. Inspired by european flight booking websites.
- Host: GitHub
- URL: https://github.com/smastrom/react-email-autocomplete
- Owner: smastrom
- License: mit
- Created: 2022-09-17T09:38:41.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2023-12-10T21:39:53.000Z (11 months ago)
- Last Synced: 2024-10-13T10:33:42.600Z (26 days ago)
- Topics: react, react-autocomplete, react-autosuggest, react-email, react-email-component
- Language: TypeScript
- Homepage: https://react-email-autocomplete.netlify.app
- Size: 412 KB
- Stars: 14
- Watchers: 2
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# React Email Autocomplete
![npm](https://img.shields.io/npm/v/@smastrom/react-email-autocomplete?color=46c119) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/smastrom/react-email-autocomplete/tests.yml?branch=main&label=tests)
![dependency-count](https://img.shields.io/badge/dependency%20count-0-success)| Before typing `@` | After typing `@` (optional) |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| ![@smastrom/react-email-autocomplete](https://i.ibb.co/SNTkHJQ/Screenshot-2022-12-07-alle-13-50-59.png) | ![@smastrom/react-email-autocomplete](https://i.ibb.co/DWQBQw7/Screenshot-2022-12-07-alle-13-54-23.png) |
**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 `` element or control it with React Hook Form[Demo and examples](https://@smastrom/react-email-autocomplete.netlify.app) — [Stackblitz](https://stackblitz.com/edit/react-4kufqv?file=src/App.js) — [NextJS](https://stackblitz.com/edit/stackblitz-starters-f36nmm?file=app%2Fpage.tsx)
## :floppy_disk: Installation
```bash
pnpm add @smastrom/react-email-autocomplete
# npm i @smastrom/react-email-autocomplete
# yarn add @smastrom/react-email-autocomplete
```
## :art: Usage / Styling
The component renders a single `div` with a very simple structure:
```js
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:
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```NextJS App Router
`components/Email.tsx`
```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 (
customSetter(newValue)
value={email}
/>
)
}
````app/page.tsx`
```tsx
import { Email } from '@/components/Email'export default function Home() {
return (
{/* ... */}
{/* ... */}
)
}
```TypeScript
```ts
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`.
```json
"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:
```css
.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).
```css
.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:
```jsx
```
```css
.my-suggestion[data-custom-attr='true'] {
background-color: aliceblue;
}
```## :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 `@` |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| ![@smastrom/react-email-autocomplete](https://i.ibb.co/SNTkHJQ/Screenshot-2022-12-07-alle-13-50-59.png) | ![@smastrom/react-email-autocomplete](https://i.ibb.co/ZgWCPkg/Screenshot-2022-12-07-alle-13-52-46.png) |```jsx
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 (
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](https://i.ibb.co/SNTkHJQ/Screenshot-2022-12-07-alle-13-50-59.png) | ![@smastrom/react-email-autocomplete](https://i.ibb.co/DWQBQw7/Screenshot-2022-12-07-alle-13-54-23.png) |All you have to do is to provide a second array of domains to `refineList` prop. This package ships with a [curated list](https://github.com/smastrom/@smastrom/react-email-autocomplete/blob/main/src/domains.json) of the ~160 most popular world domains that you can directly import and use (thanks to **@mailcheck**):
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```Alternatively, you can use your own array of domains or [search]() for the one that best suits your audience.
## :globe_with_meridians: Localization
This package ships with an optional hook that simplifies managing different lists of domains according to the [browser's locale](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language).
**1 - Create an object and define lists for each browser locale:**
```js
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
```ts
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](https://www.localeplanet.com/icu/iso639.html) 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:**
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```### Usage with internationalization frameworks or SSR
To manually set the locale, pass its code as second argument:
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```Or with NextJS App router:
`components/Email.tsx`
```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 (
)
}
````app/page.tsx`
```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 (
)
}
```
## :8ball: onSelect callback
To invoke a callback everytime a suggestion is selected (either with mouse or keyboard), pass a callback to `onSelect` prop:
```jsx
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 (
customSetter(newValue)
onSelect={handleSelect}
value={email}
/>
)
}
```Type Definition
```ts
type OnSelectData = {
value: string
keyboard: boolean
position: number
}type OnSelect = (object: OnSelectData) => void | Promise
```
## :cyclone: Props
| Prop | Description | Type | Default | Required |
| ------------------- | ----------------------------------------------------- | -------------------------------------- | ------------------- | ------------------ |
| `value` | State or portion of state that holds the email value | _string_ | undefined | :white_check_mark: |
| `onChange` | State setter or custom dispatcher to update the email | _OnChange_ | undefined | :white_check_mark: |
| `baseList` | Domains to suggest while typing the username | _string[]_ | undefined | :white_check_mark: |
| `refineList` | Domains to refine suggestions after typing `@` | _string[]_ | [] | :x: |
| `onSelect` | Custom callback on suggestion select | _OnSelect_ | () => {} | :x: |
| `minChars` | Minimum chars required to display suggestions | _1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8_ | 2 | :x: |
| `maxResults` | Maximum number of suggestions to display | _2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8_ | 6 | :x: |
| `classNames` | Class names for each element | _ClassNames_ | undefined | :x: |
| `className` | Class name of the root element | _string_ | undefined | :x: |
| `activeDataAttr` | Attribute name to set on focused/hovered suggestion | _string_ | `data-active-email` | :x: |
| `dropdownAriaLabel` | Aria label for the dropdown list | _string_ | `Suggestions` | :x: |:bulb: 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: 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](https://react-hook-form.com/api/usecontroller/controller).
## :dvd: License
MIT