Typeahead is auto suggestion or completion based on what the user is starting to type, gets refined as the user types more.
import React from 'react' import Typeahead from 'playbook-ui' const options = [ { label: 'Orange', value: '#FFA500' }, { label: 'Red', value: '#FF0000' }, { label: 'Green', value: '#00FF00' }, { label: 'Blue', value: '#0000FF' }, ] const TypeaheadDefault = (props) => { return ( <Typeahead label="Colors" options={options} /> ) } export default TypeaheadDefault
/* eslint-disable react/no-danger */ /* eslint-disable react/no-multi-comp */ import React, { useState } from 'react' import { components } from 'react-select' import { Avatar, Body, Flex, FlexItem, Title, Typeahead, } from 'playbook-ui' const USERS = [ { name: "Wade Winningham", title: "Nitro Principal Developer", territory: "PHL", }, { name: "Jason Cypret", title: "Vice President of User Experience", territory: "PHL", }, { name: "Stephen Marshall", title: "Senior User Experience Engineer", territory: "PHL", }, { name: "Jasper Furniss", title: "Senior User Experience Engineer", territory: "PHL", }, ]; const TypeaheadWithHighlight = (props) => { const [selectedUser, setSelectedUser] = useState() const formatOptionLabel = ({name, territory, title}, {inputValue}) => { const highlighted = (text) => { if (!inputValue.length) return text return text.replace( new RegExp(inputValue, 'gi'), (highlighted) => `<mark>${highlighted}</mark>` ) } return ( <Flex> <FlexItem> <Avatar marginRight="sm" name={name} size="sm" /> </FlexItem> <FlexItem> <Title size={4} > <span dangerouslySetInnerHTML={{ __html: highlighted(name) }} /></Title> <Body color="light" > <span dangerouslySetInnerHTML={{ __html: highlighted(title) }} />{" • "} {territory} </Body> </FlexItem> </Flex> ) } const customComponents = { Option: (highlightProps) => ( <components.Option {...highlightProps}/> ), SingleValue: ({ ...props }) => ( <components.SingleValue> <span>{props.data.name}</span> </components.SingleValue> ) } return ( <React.Fragment> <Typeahead components={customComponents} formatOptionLabel={formatOptionLabel} getOptionLabel={(option) => option.name} getOptionValue={({name, title}) => `${name} ${title}`} label="Users" onChange={(user) => setSelectedUser(user)} options={USERS.filter((option) => option.name != selectedUser?.name)} placeholder="type the name of a user" /> </React.Fragment> ) } export default TypeaheadWithHighlight
Typeahead kit is data-driven. The minimum default fields are label
and value
.
This is an example of an option: { label: 'Windows', value: '#FFA500' }
You can also pass default_options
which will populate the initial pill selections:
default_options: [{ label: 'Windows', value: '#FFA500' }]
JavaScript events are triggered based on actions you take within the kit such as selection, removal and clearing.
This kit utilizes a default id
prop named react-select-input
. It is highly advised to send your own unique id
prop when using this kit to ensure these events do not unintentionally affect other instances of the kit in the same view. The examples below will use the unique id
prop named typeahead-pills-example1
:
pb-typeahead-kit-typeahead-pills-example1-result-option-select
event to perform custom work when an option is clicked.
pb-typeahead-kit-typeahead-pills-example1-result-option-remove
event to perform custom work when a pill is clicked.
pb-typeahead-kit-typeahead-pills-example1-result-option-clear
event to perform custom work when all pills are removed upon clicking the X.
The same rule regarding the id
prop applies to publishing JS events. The examples below will use the unique id
prop named typeahead-pills-example1
:
pb-typeahead-kit-typeahead-pills-example1:clear
event to clear all options.
import React from 'react' import { Typeahead } from 'playbook-ui' const options = [ { label: 'Windows', value: '#FFA500' }, { label: 'Siding', value: '#FF0000' }, { label: 'Doors', value: '#00FF00' }, { label: 'Roofs', value: '#0000FF' }, ] const TypeaheadWithPills = (props) => { return ( <> <Typeahead isMulti label="Colors" options={options} placeholder="" /> </> ) } export default TypeaheadWithPills
load_options
Promise *Additional required props: * async: true
, pills: true
The prop load_options
, when used in conjunction with async: true
and pills: true
, points to a JavaScript function located within the global window namespace. This function should return a Promise
which resolves with the list of formatted options as described in prior examples above. This function is identical to the function provided to the React version of this kit. See the code example for more details.
loadOptions
*Additional required props: * async: true
As outlined in the react-select Async docs, loadOptions
expects to return a Promise that resolves resolves with the list of formatted options as described in prior examples above. See the code example for more details.
getOptionLabel
+ getOptionValue
If your server returns data that requires differing field names other than label
and value
See react-select
docs for more information: https://react-select.com/advanced#replacing-builtins
import React, { useState } from 'react' import { Caption, Typeahead, User, } from 'playbook-ui' /** * * @const filterResults * @ignore * @returns {[Object]} - a custom mapping of objects, minimally containing * `value` and `label` among other possible fields * @summary - for doc example purposes only */ const filterResults = (results) => results.items.map((result) => { return { name: result.login, id: result.id, } }) /** * * @const promiseOptions * @ignore * @returns {Promise} - fetch API data results from Typeahead input text * @see - https://react-select.com/home#async * @summary - for doc example purposes only */ const promiseOptions = (inputValue) => new Promise((resolve) => { if (inputValue) { fetch(`https://api.github.com/search/users?q=${inputValue}`) .then((response) => response.json()) .then((results) => resolve(filterResults(results))) } else { resolve([]) } }) const TypeaheadWithPillsAsync = (props) => { const [users, setUsers] = useState([]) const handleOnChange = (value) => setUsers(formatUsers(value)) const formatValue = (users) => formatUsers(users) const formatUsers = (users) => { const results = () => (users.map((user) => { if (Object.keys(user)[0] === 'name' || Object.keys(user)[1] === 'id'){ return ({ label: user.name, value: user.id }) } else { return user } })) return results() } return ( <> {users && users.length > 0 && ( <React.Fragment> <Caption marginBottom="xs" text="State (Users)" /> {users.map((user) => ( <User align="left" key={user.value} marginBottom="md" name={user.label} orientation="horizontal" /> ))} </React.Fragment> )} <Typeahead async getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} isMulti label="Github Users" loadOptions={promiseOptions} onChange={handleOnChange} placeholder="type the name of a Github user" value={formatValue(users)} /> </> ) } export default TypeaheadWithPillsAsync
If the data field imageUrl
is present, FormPill will receive that field as a prop and display the image.
import React, { useState } from 'react' import { Caption, Typeahead, User, } from 'playbook-ui' /** * * @const filterResults * @ignore * @returns {[Object]} - a custom mapping of objects, minimally containing * `value` and `label` among other possible fields * @summary - for doc example purposes only */ const filterResults = (results) => results.items.map((result) => { return { imageUrl: result.avatar_url, //add the custom field label: result.login, value: result.id, } }) /** * * @const promiseOptions * @ignore * @returns {Promise} - fetch API data results from Typeahead input text * @see - https://react-select.com/home#async * @summary - for doc example purposes only */ const promiseOptions = (inputValue) => new Promise((resolve) => { if (inputValue) { fetch(`https://api.github.com/search/users?q=${inputValue}`) .then((response) => response.json()) .then((results) => resolve(filterResults(results))) } else { resolve([]) } }) const TypeaheadWithPillsAsyncUsers = (props) => { const [users, setUsers] = useState([]) const handleOnChange = (value) => setUsers(value) /** * * @const handleOnMultiValueClick {function} - a custom callback for the MultiValue click * @ignore * @returns {null} * @summary - for doc example purposes only */ const handleOnMultiValueClick = (value) => { alert(`You removed the user: "${value.label}"`) } return ( <> {users && users.length > 0 && ( <React.Fragment> <Caption marginBottom="xs" text="State (Users)" /> {users.map((user) => ( <User align="left" avatar avatarUrl={user.imageUrl} key={user.value} marginBottom="md" name={user.label} orientation="horizontal" /> ))} </React.Fragment> )} <Typeahead async isMulti label="Github Users" loadOptions={promiseOptions} noOptionsMessage={() => 'Type to Search'} onChange={handleOnChange} onMultiValueClick={handleOnMultiValueClick} placeholder="type the name of a Github user" /> </> ) } export default TypeaheadWithPillsAsyncUsers
Use valueComponent
props to pass your desire custom options. valueComponent
will be displayed if present.
import React, { useState } from 'react' import { Caption, Typeahead, User, } from 'playbook-ui' /** * * @const filterResults * @ignore * @returns {[Object]} - a custom mapping of objects, minimally containing * `value` and `label` among other possible fields * @summary - for doc example purposes only */ const filterResults = (results) => results.items.map((result) => { return { imageUrl: result.avatar_url, //add the custom field label: result.login, value: result.id, territory: 'PHL', type: result.type, } }) const promiseOptions = (inputValue) => new Promise((resolve) => { if (inputValue) { fetch(`https://api.github.com/search/users?q=${inputValue}`) .then((response) => response.json()) .then((results) => resolve(filterResults(results))) } else { resolve([]) } }) const TypeaheadWithPillsAsyncCustomOptions = (props) => { const [users, setUsers] = useState([]) const handleOnChange = (value) => setUsers(value) /** * * @const handleOnMultiValueClick {function} - a custom callback for the MultiValue click * @ignore * @returns {null} * @summary - for doc example purposes only */ const handleOnMultiValueClick = (value) => { alert(`You removed the user: "${value.label}"`) } return ( <> {users && users.length > 0 && ( <React.Fragment> <Caption marginBottom="xs" text="State (Users)" /> {users.map((user) => ( <User align="left" avatar avatarUrl={user.imageUrl} key={user.value} marginBottom="md" name={user.label} orientation="horizontal" /> ))} </React.Fragment> )} <Typeahead async isMulti label="Github Users" loadOptions={promiseOptions} onChange={handleOnChange} onMultiValueClick={handleOnMultiValueClick} placeholder="type the name of a Github user" valueComponent={(props) => ( <User avatar avatarUrl={props.imageUrl} name={props.label} territory={props.territory} title={props.type} /> )} /> </> ) } export default TypeaheadWithPillsAsyncCustomOptions
import React from 'react' import Typeahead from 'playbook-ui' const synths = [ { label: 'Oberheim', value: 'OBXa' }, { label: 'Moog', value: 'Minimoog' }, { label: 'Roland', value: 'Juno' }, { label: 'Korg', value: 'MS-20' }, ] const cities = [ { label: 'Budapest', value: 'Hungary' }, { label: 'Singapore', value: 'Singapore' }, { label: 'Oslo', value: 'Norway' }, { label: 'Lagos', value: 'Nigeria' }, ] const TypeaheadInline = (props) => { return ( <> <Typeahead inline isMulti label="Synths" options={synths} /> <Typeahead inline isMulti label="Placeholder Plus Icon" options={cities} placeholder="Add cities" plusIcon /> </> ) } export default TypeaheadInline
import React from 'react' import Typeahead from 'playbook-ui' const labels = [ { label: 'Verve', value: '1956' }, { label: 'Stax', value: '1957' }, { label: 'Motown', value: '1959' }, { label: 'Kudu', value: '1971' }, { label: 'Stones Throw', value: '1996' }, ] const expressionists = [ { label: 'Kandinsky', value: 'Russia' }, { label: 'Klee', value: 'Switzerland' }, { label: 'Kokoschka', value: 'Austria' }, { label: 'Kirchner', value: 'Germany' }, ] const TypeaheadMultiKit = (props) => { return ( <> <Typeahead defaultValue={[labels[0]]} isMulti label="Badges" multiKit="badge" options={labels} /> <Typeahead defaultValue={[expressionists[0]]} isMulti label="Small Pills" multiKit="smallPill" options={expressionists} /> </> ) } export default TypeaheadMultiKit
import React from 'react' import { Typeahead } from 'playbook-ui' const options = [ { label: 'Jardim', value: 'Portuguese' }, { label: 'Garten', value: 'German' }, { label: 'Giardino', value: 'Italian' }, { label: 'Jardín', value: 'Spanish' }, ] const TypeaheadCreateable = (props) => { return ( <Typeahead createable isMulti label="User Created Options" options={options} /> ) } export default TypeaheadCreateable
import React from 'react' import { Typeahead } from 'playbook-ui' /** * * @const filterResults * @ignore * @returns {[Object]} - a custom mapping of objects, minimally containing * `value` and `label` among other possible fields * @summary - for doc example purposes only */ const filterResults = (results) => results.items.map((result) => { return { label: result.login, value: result.id, } }) /** * * @const promiseOptions * @ignore * @returns {Promise} - fetch API data results from Typeahead input text * @see - https://react-select.com/home#async * @summary - for doc example purposes only */ const promiseOptions = (inputValue) => new Promise((resolve) => { if (inputValue) { fetch(`https://api.github.com/search/users?q=${inputValue}`) .then((response) => response.json()) .then((results) => { resolve(results.items ? filterResults(results) : []) }) } else { resolve([]) } }) const TypeaheadAsyncCreateable = (props) => { return ( <Typeahead async createable isMulti label="Existing or User Created Options" loadOptions={promiseOptions} /> ) } export default TypeaheadAsyncCreateable
Typeahead w/ Error shows that an option must be selected or the selected option is invalid (i.e., when used in a form it signals a user to fix an error).
import React, { useState, useEffect } from 'react' import Typeahead from 'playbook-ui' const options = [ { label: 'Orange', value: '#FFA500' }, { label: 'Red', value: '#FF0000' }, { label: 'Green', value: '#00FF00' }, { label: 'Blue', value: '#0000FF' }, ] const TypeaheadErrorState = (props) => { const [errorState, setErrorState] = useState("Please make a valid selection"); const [searchValue, setSearchValue] = useState(null); const handleOnChange = (value) => setSearchValue(value) useEffect(() => { if(searchValue) { setErrorState("") } else { setErrorState("Please make a valid selection") } }, [searchValue]) return ( <Typeahead error={errorState} label="Colors" onChange={handleOnChange} options={options} /> ) } export default TypeaheadErrorState
import React, { useState } from 'react' import { Button, } from 'playbook-ui' import Typeahead from 'playbook-ui' const options = [ { label: 'Orange', value: '#FFA500' }, { label: 'Red', value: '#FF0000' }, { label: 'Green', value: '#00FF00' }, { label: 'Blue', value: '#0000FF' }, { label: 'Amaranth', value: '#9F2B68' }, { label: 'Key Lime', value: '#DAF7A6' }, { label: 'Turquois', value: '#00FFD0' }, ] const TypeaheadCustomMenuList = (props) => { const defaultColorOptions = options.slice(0, 3) const [colorOptions, setColorOptions] = useState(defaultColorOptions) const moreToLoad = colorOptions.length == defaultColorOptions.length const loadColors = moreToLoad ? () => setColorOptions(options) : () => setColorOptions(defaultColorOptions) const menuListProps = { footer: (<Button margin="sm" onClick={loadColors} text={`Load ${moreToLoad ? "More" : "Less"}`} />) } const MenuList = (props) => ( <Typeahead.MenuList {...menuListProps} /> ) return ( <Typeahead components={{ MenuList }} label="Colors" options={colorOptions} /> ) } export default TypeaheadCustomMenuList
import React from 'react' import Typeahead from 'playbook-ui' const options = [ { label: 'Orange', value: '#FFA500' }, { label: 'Red', value: '#FF0000' }, { label: 'Green', value: '#00FF00' }, { label: 'Blue', value: '#0000FF' }, ] const TypeaheadMarginBottom = (props) => { return ( <> <Typeahead label="None" marginBottom="none" options={options} /> <Typeahead label="XXS" marginBottom="xxs" options={options} /> <Typeahead label="XS" marginBottom="xs" options={options} /> <Typeahead label="Default - SM" options={options} /> <Typeahead label="MD" marginBottom="md" options={options} /> <Typeahead label="LG" marginBottom="lg" options={options} /> <Typeahead label="XL" marginBottom="xl" options={options} /> </> ) } export default TypeaheadMarginBottom
Change the form pill color by passing the optional pillColor
prop. Product, Data, and Status colors are available options. Check them out here in the Form Pill colors example.
import React from 'react' import { Typeahead } from 'playbook-ui' const options = [ { label: 'Windows', value: '#FFA500' }, { label: 'Siding', value: '#FF0000' }, { label: 'Doors', value: '#00FF00' }, { label: 'Roofs', value: '#0000FF' }, ] const TypeaheadWithPills = (props) => { return ( <> <Typeahead isMulti label="Colors" options={options} pillColor="neutral" placeholder="" /> </> ) } export default TypeaheadWithPills
Avoid using on questionaires, surverys, text input and textarea when users answers/inputs will be individualized.