Repository: laboratoriobridge/bold
Pull request #475 (2019-10-01 16:54:45)

Create prop 'createNewItem' for Select to allow Typeahead functionality


Patch:
diff --git a/site/components/Tags.tsx b/site/components/Tags.tsx
deleted file mode 100644
index d1cf8b4da..000000000
--- a/site/components/Tags.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react'
-
-import { Tag, TagProps, useTheme } from '../../lib'
-
-export function TagDone(props: TagProps) {
-  const theme = useTheme()
-
-  return (
-    <Tag
-      style={{
-        color: theme.pallete.status.success.c40,
-        backgroundColor: theme.pallete.status.success.c90,
-        fontWeight: 400,
-      }}
-      {...props}
-    />
-  )
-}
-
-export function TagNormal(props: TagProps) {
-  const theme = useTheme()
-
-  return (
-    <Tag
-      style={{
-        color: theme.pallete.gray.c40,
-        backgroundColor: theme.pallete.gray.c90,
-        fontWeight: 400,
-      }}
-      {...props}
-    />
-  )
-}
diff --git a/site/pages.ts b/site/pages.ts
index 5d6ab2ced..d4429dadb 100644
--- a/site/pages.ts
+++ b/site/pages.ts
@@ -37,7 +37,6 @@ export default [
     icon: 'bricksFilled',
     href: '/components',
     children: [
-      { href: '/components/overview', title: 'Overview' },
       { href: '/components/accordion', title: 'Accordion' },
       { href: '/components/alert', title: 'Alert' },
       { href: '/components/breadcrumb', title: 'Breadcrumb' },
diff --git a/site/pages/components/overview/index.mdx b/site/pages/components/overview/index.mdx
deleted file mode 100644
index 54eaeb7dd..000000000
--- a/site/pages/components/overview/index.mdx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead, Tag } from '../../../../lib'
-import { TagDone, TagNormal } from '../../../components/Tags'
-
-# Overview
-
-Track the status of components across design and development. You can also see new components and the next ones to come.
-
-## Component status
-
-<Table>
-
-<TableHead>
-  <TableRow>
-    <TableHeader>Component</TableHeader>
-    <TableHeader>Design</TableHeader>
-    <TableHeader>Development</TableHeader>
-  </TableRow>
-</TableHead>
-<TableBody>
-    <TableRow>
-        <TableCell>Accordion</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagNormal>Upcoming</TagNormal></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Alert</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Breadcrumb</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Button</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Cards</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagNormal>Upcoming</TagNormal></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Checkbox</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Content Switch</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Date Picker</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Dropdown</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>File Uploader</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Form</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Link</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Modal</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Multi Select</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-    <TableRow>
-        <TableCell>Paginator</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-     </TableRow>
-     <TableRow>
-        <TableCell>Form</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Link</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Modal</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Multi Select</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Paginator</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Radio Button</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Search</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Select</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Switch</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Table</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Tabs</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Tag</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Text Field</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Toast</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagNormal>Upcoming</TagNormal></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Tooltip</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-    </TableRow>
-    <TableRow>
-        <TableCell>Wizard</TableCell>
-        <TableCell><TagDone>Done</TagDone></TableCell>
-        <TableCell><TagNormal>Upcoming</TagNormal></TableCell>
-    </TableRow>
-</TableBody>
-
-</Table>
diff --git a/site/pages/components/select/Select.demo.tsx b/site/pages/components/select/Select.demo.tsx
index 89c359466..454695128 100644
--- a/site/pages/components/select/Select.demo.tsx
+++ b/site/pages/components/select/Select.demo.tsx
@@ -6,9 +6,7 @@ function SelectDemo() {
   const itemToString = (item: any) => item
 
   return (
-    <FormControl label='Favorite pasta' required>
-      <Select<string> items={items} itemToString={itemToString} name='favorite pasta' required />
-    </FormControl>
+    <Select<string> label='Favorite pasta' items={items} itemToString={itemToString} name='favorite pasta' required />
   )
 }
 
diff --git a/site/pages/components/select/Suggestion.demo.tsx b/site/pages/components/select/Suggestion.demo.tsx
new file mode 100644
index 000000000..4ae94093c
--- /dev/null
+++ b/site/pages/components/select/Suggestion.demo.tsx
@@ -0,0 +1,31 @@
+import { useState } from 'react'
+
+import { Select, Text, VFlow } from '../../../../lib'
+
+const items = ['Carbonara', 'Gnocchi', 'Lasagna', 'Macaroni and Cheese', 'Pesto', 'Pizza']
+
+function SuggestionDemo() {
+  const [value, setValue] = useState()
+
+  const itemToString = (item: any) => item
+  const createNewItem = (inputValue: string) => inputValue
+
+  return (
+    <VFlow>
+      <Text>Selected item: {value || '[none]'}</Text>
+      <Select<string>
+        value={value}
+        label='Favorite pasta'
+        name='favorite pasta'
+        items={items}
+        onChange={setValue}
+        itemToString={itemToString}
+        createNewItem={createNewItem}
+        icon={null}
+        required
+      />
+    </VFlow>
+  )
+}
+
+export default SuggestionDemo
diff --git a/site/pages/components/select/index.mdx b/site/pages/components/select/index.mdx
index 533c2d51b..a41d2e874 100644
--- a/site/pages/components/select/index.mdx
+++ b/site/pages/components/select/index.mdx
@@ -13,3 +13,11 @@ Select is a type of input that is used in forms, where a user is submitting data
 ## Example
 
 <Demo src='pages/components/select/Select' />
+
+## Suggestion
+
+Allow select value to be whatever the user types, by providing a `createNewItem` prop.
+
+You can use the prop `openOnFocus` to control whether the suggestion menu should appear when input receives focus - default is false for suggestion selects (when createNewItem) and true for normal selects.
+
+<Demo src='pages/components/select/Suggestion' />
diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx
index 6bf90e3fe..61b3bffe2 100644
--- a/src/components/Select/Select.stories.tsx
+++ b/src/components/Select/Select.stories.tsx
@@ -42,6 +42,27 @@ storiesOf('Components|Select', module)
       multiple={boolean('multiple', false)}
       clearable={boolean('clearable', true)}
       disabled={boolean('disabled', false)}
+      openOnFocus={boolean('openOnFocus', true)}
+      loading={boolean('loading', false)}
+      onChange={action('changed')}
+      onBlur={action('blur')}
+    />
+  ))
+  .add('suggestion', () => (
+    <Select<DefaultItemType>
+      label='Fruit'
+      name='fruit'
+      items={fruits}
+      error={text('error', '')}
+      icon={null}
+      createNewItem={str => ({ value: str, label: str })}
+      itemToString={item => item && item.label}
+      itemIsEqual={(a, b) => a.value === b.value}
+      placeholder='Select a value...'
+      multiple={boolean('multiple', false)}
+      clearable={boolean('clearable', true)}
+      disabled={boolean('disabled', false)}
+      openOnFocus={boolean('openOnFocus', false)}
       loading={boolean('loading', false)}
       onChange={action('changed')}
       onBlur={action('blur')}
@@ -59,6 +80,7 @@ storiesOf('Components|Select', module)
       multiple={boolean('multiple', false)}
       clearable={boolean('clearable', true)}
       disabled={boolean('disabled', false)}
+      openOnFocus={boolean('openOnFocus', true)}
       onChange={action('changed')}
       onBlur={action('blur')}
     />
@@ -83,6 +105,7 @@ storiesOf('Components|Select', module)
       multiple={boolean('multiple', false)}
       clearable={boolean('clearable', true)}
       disabled={boolean('disabled', false)}
+      openOnFocus={boolean('openOnFocus', true)}
       loading={boolean('loading', false)}
       onChange={action('changed')}
       onBlur={action('blur')}
diff --git a/src/components/Select/SelectMenu/SelectMenuItem.tsx b/src/components/Select/SelectMenu/SelectMenuItem.tsx
index 9c0c24cf0..0a8926076 100644
--- a/src/components/Select/SelectMenu/SelectMenuItem.tsx
+++ b/src/components/Select/SelectMenu/SelectMenuItem.tsx
@@ -26,7 +26,12 @@ export function SelectMenuItem(props: SelectMenuItemProps) {
 export function SelectHelperMenuItem(props: SelectMenuItemProps) {
   const theme = useTheme()
 
-  return <SelectMenuItem style={{ background: theme.pallete.surface.background }} {...props} />
+  return (
+    <SelectMenuItem
+      style={{ background: theme.pallete.surface.background, paddingTop: '0.25rem', paddingBottom: '0.25rem' }}
+      {...props}
+    />
+  )
 }
 
 export function SelectLoadingItem(props: SelectMenuItemProps) {
@@ -47,6 +52,12 @@ export function SelectEmptyItem(props: SelectMenuItemProps) {
   return <SelectHelperMenuItem {...props}>{locale.select.emptyItem}</SelectHelperMenuItem>
 }
 
+export function SelectCreateItem(props: SelectMenuItemProps) {
+  const locale = useLocale()
+
+  return <SelectHelperMenuItem {...props}>{locale.select.createItem}</SelectHelperMenuItem>
+}
+
 export const createStyles = (theme: Theme) => ({
   item: {
     ...theme.typography.variant('main'),
diff --git a/src/components/Select/SelectSingle/SelectDownshift.tsx b/src/components/Select/SelectSingle/SelectDownshift.tsx
index 146312aac..45998e6ac 100644
--- a/src/components/Select/SelectSingle/SelectDownshift.tsx
+++ b/src/components/Select/SelectSingle/SelectDownshift.tsx
@@ -1,6 +1,6 @@
-import Downshift, { ControllerStateAndHelpers, DownshiftProps, StateChangeOptions } from 'downshift'
+import Downshift, { ControllerStateAndHelpers, DownshiftProps, DownshiftState, StateChangeOptions } from 'downshift'
 import matchSorter from 'match-sorter'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useMemo, useState } from 'react'
 
 export interface SelectDownshiftProps<T> extends DownshiftProps<T> {
   /**
@@ -8,6 +8,11 @@ export interface SelectDownshiftProps<T> extends DownshiftProps<T> {
    */
   items: T[]
 
+  /**
+   * Whether the select menu should be opened when the select is focused.
+   */
+  openOnFocus?: boolean
+
   children?(props: SelectDownshiftRenderProps<T>): React.ReactNode
 
   /**
@@ -16,6 +21,13 @@ export interface SelectDownshiftProps<T> extends DownshiftProps<T> {
    * @param downshift The downshift controller and helpers.
    */
   onFilterChange?(filter: string, downshift: SelectDownshiftRenderProps<T>): void
+
+  /**
+   * If informed, select will allow insertion of items not present on the select.
+   * @param inputValue The string type in the text box
+   * @return The value that should be created and hold by the select
+   */
+  createNewItem?(inputValue: string): T
 }
 
 export interface SelectDownshiftRenderProps<T> extends ControllerStateAndHelpers<T>, State<T> {
@@ -42,7 +54,9 @@ export function defaultSelectFilter<T>(
  * Downshift extension with item and filter management.
  */
 export function SelectDownshift<T>(props: SelectDownshiftProps<T>) {
-  const { items, onFilterChange, children, ...rest } = props
+  const { items, onFilterChange, children, createNewItem, openOnFocus, ...rest } = props
+
+  const stateReducer = useMemo(() => createReducer(props), [openOnFocus, props.stateReducer])
 
   const [visibleItems, setVisibleItems] = useState<T[]>(items)
   useEffect(() => {
@@ -50,6 +64,10 @@ export function SelectDownshift<T>(props: SelectDownshiftProps<T>) {
   }, [props.items])
 
   const handleStateChange = (options: StateChangeOptions<T>, downshift: ControllerStateAndHelpers<T>) => {
+    if (createNewItem && options.hasOwnProperty('inputValue')) {
+      rest.onChange && rest.onChange(createNewItem(options.inputValue), getStateAndHelpers(downshift))
+    }
+
     if (options.isOpen) {
       onFilterChange(null, getStateAndHelpers(downshift))
     }
@@ -80,7 +98,7 @@ export function SelectDownshift<T>(props: SelectDownshiftProps<T>) {
   })
 
   return (
-    <Downshift {...rest} onStateChange={handleStateChange} onChange={handleChange}>
+    <Downshift {...rest} stateReducer={stateReducer} onStateChange={handleStateChange} onChange={handleChange}>
       {downshift => children(getStateAndHelpers(downshift))}
     </Downshift>
   )
@@ -92,3 +110,15 @@ SelectDownshift.defaultProps = {
     setVisibleItems(defaultSelectFilter(items, filter, itemToString))
   },
 } as Partial<SelectDownshiftProps<any>>
+
+function createReducer(props: SelectDownshiftProps<any>) {
+  const { openOnFocus = props.createNewItem ? false : undefined, stateReducer } = props
+
+  return (state: DownshiftState<any>, changes: StateChangeOptions<any>) => {
+    if (openOnFocus === false && changes.type === undefined && !state.isOpen && changes.isOpen) {
+      return { ...changes, isOpen: false }
+    }
+
+    return stateReducer ? stateReducer(state, changes) : changes
+  }
+}
diff --git a/src/components/Select/SelectSingle/SelectDownshiftMenu.tsx b/src/components/Select/SelectSingle/SelectDownshiftMenu.tsx
index 545380623..9b134de3c 100644
--- a/src/components/Select/SelectSingle/SelectDownshiftMenu.tsx
+++ b/src/components/Select/SelectSingle/SelectDownshiftMenu.tsx
@@ -3,12 +3,13 @@ import React, { CSSProperties } from 'react'
 
 import { useStyles } from '../../../styles'
 import { SelectEmptyItem, SelectLoadingItem, SelectMenu, SelectMenuItem } from '../SelectMenu'
+import { SelectCreateItem } from '../SelectMenu/SelectMenuItem'
 
 export interface SelectDownshiftMenuProps<T> {
   items: T[]
   loading: boolean
   downshift: ControllerStateAndHelpers<T>
-
+  createNewItem?: boolean
   components?: Partial<SelectMenuComponents<T>>
 
   /**
@@ -20,6 +21,7 @@ export interface SelectDownshiftMenuProps<T> {
 }
 
 export interface SelectMenuComponents<T> {
+  CreateItem: React.ComponentType<SelectDownshiftMenuProps<T>>
   LoadingItem: React.ComponentType<SelectDownshiftMenuProps<T>>
   EmptyItem: React.ComponentType<SelectDownshiftMenuProps<T>>
   Item: React.ComponentType<SelectDownshiftMenuProps<T> & { item: T; index: number }>
@@ -30,11 +32,12 @@ export function SelectDownshiftMenu<T>(props: SelectDownshiftMenuProps<T>) {
     items,
     loading: isLoading,
     components,
+    createNewItem,
     downshift: { isOpen, getMenuProps },
   } = props
 
   const { classes } = useStyles(createStyles)
-  const { LoadingItem, EmptyItem, Item } = { ...defaultComponents, ...components }
+  const { CreateItem, LoadingItem, EmptyItem, Item } = { ...defaultComponents, ...components }
 
   return (
     <div className={classes.wrapper}>
@@ -42,7 +45,9 @@ export function SelectDownshiftMenu<T>(props: SelectDownshiftMenuProps<T>) {
         <SelectMenu {...getMenuProps({ refKey: 'menuRef' })}>
           {isLoading && <LoadingItem {...props} />}
 
-          {!isLoading && (!items || items.length === 0) && <EmptyItem {...props} />}
+          {!isLoading && createNewItem && (items || []).length > 0 && <CreateItem {...props} />}
+
+          {!isLoading && !createNewItem && (items || []).length === 0 && <EmptyItem {...props} />}
 
           {items && items.map((item, index) => <Item key={index} index={index} item={item} {...props} />)}
         </SelectMenu>
@@ -52,6 +57,7 @@ export function SelectDownshiftMenu<T>(props: SelectDownshiftMenuProps<T>) {
 }
 
 export const defaultComponents: SelectMenuComponents<any> = {
+  CreateItem: (props: SelectDownshiftMenuProps<any>) => <SelectCreateItem />,
   LoadingItem: (props: SelectDownshiftMenuProps<any>) => <SelectLoadingItem />,
   EmptyItem: (props: SelectDownshiftMenuProps<any>) => <SelectEmptyItem />,
   Item: (props: SelectDownshiftMenuProps<any> & { item: any; index: number }) => {
diff --git a/src/components/Select/SelectSingle/SelectSingle.tsx b/src/components/Select/SelectSingle/SelectSingle.tsx
index dec37cf10..a4ea7b9bb 100644
--- a/src/components/Select/SelectSingle/SelectSingle.tsx
+++ b/src/components/Select/SelectSingle/SelectSingle.tsx
@@ -23,7 +23,9 @@ export interface SelectSingleProps<T = DefaultItemType>
   itemToString: SelectDownshiftProps<T>['itemToString']
   onChange?: SelectDownshiftProps<T>['onChange']
   isOpen?: SelectDownshiftProps<T>['isOpen']
+  openOnFocus?: SelectDownshiftProps<T>['openOnFocus']
   onFilterChange?: SelectDownshiftProps<T>['onFilterChange']
+  createNewItem?: SelectDownshiftProps<T>['createNewItem']
 
   loading?: SelectDownshiftMenuProps<T>['loading']
   renderItem?: SelectDownshiftMenuProps<T>['renderItem']
@@ -37,7 +39,9 @@ export function SelectSingle<T>(props: SelectSingleProps<T>) {
     itemToString,
     onChange,
     isOpen,
+    openOnFocus,
     onFilterChange,
+    createNewItem,
     loading,
     renderItem,
     components,
@@ -79,7 +83,9 @@ export function SelectSingle<T>(props: SelectSingleProps<T>) {
         itemToString={itemToString}
         onChange={onChange}
         isOpen={isOpen}
+        openOnFocus={openOnFocus}
         onFilterChange={onFilterChange}
+        createNewItem={createNewItem}
         labelId={formControlProps.labelId}
       >
         {downshift => {
@@ -89,7 +95,7 @@ export function SelectSingle<T>(props: SelectSingleProps<T>) {
             <div className={css(style)}>
               <div>
                 <TextInput
-                  icon={isOpen ? 'zoomOutline' : downshiftOpen ? 'angleUp' : 'angleDown'}
+                  icon={downshiftOpen ? 'angleUp' : 'angleDown'}
                   {...rest}
                   onBlur={handleInputBlur(downshift)}
                   onFocus={handleInputFocus(downshift)}
@@ -108,6 +114,7 @@ export function SelectSingle<T>(props: SelectSingleProps<T>) {
                 loading={loading}
                 renderItem={renderItem}
                 components={components}
+                createNewItem={!!createNewItem}
               />
             </div>
           )
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
index 3fd679cf9..84e42876c 100644
--- a/src/i18n/locales/en-US.ts
+++ b/src/i18n/locales/en-US.ts
@@ -27,6 +27,7 @@ const locale = {
     currentPage: 'Current page',
   },
   select: {
+    createItem: 'Select an item or type a new value',
     emptyItem: 'No results were found',
     loadingItem: 'Loading...',
     removeItem: 'Remove',
diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts
index 1eaded132..2951506b0 100644
--- a/src/i18n/locales/pt-BR.ts
+++ b/src/i18n/locales/pt-BR.ts
@@ -29,6 +29,7 @@ const locale: LocaleConfiguration = {
     currentPage: 'Página atual',
   },
   select: {
+    createItem: 'Selecione um item ou digite um novo valor',
     emptyItem: 'Nenhum resultado encontrado',
     loadingItem: 'Carregando...',
     removeItem: 'Remover',


Test patch:
diff --git a/src/components/Select/SelectMenu/SelectMenuItem.test.tsx b/src/components/Select/SelectMenu/SelectMenuItem.test.tsx
index a45842c3a..f08e933ae 100644
--- a/src/components/Select/SelectMenu/SelectMenuItem.test.tsx
+++ b/src/components/Select/SelectMenu/SelectMenuItem.test.tsx
@@ -1,7 +1,13 @@
 import { render } from '@testing-library/react'
 import React from 'react'
 
-import { SelectEmptyItem, SelectHelperMenuItem, SelectLoadingItem, SelectMenuItem } from './SelectMenuItem'
+import {
+  SelectCreateItem,
+  SelectEmptyItem,
+  SelectHelperMenuItem,
+  SelectLoadingItem,
+  SelectMenuItem,
+} from './SelectMenuItem'
 
 describe('SelectMenuItem', () => {
   it('should render correctly', () => {
@@ -40,3 +46,10 @@ describe('SelectEmptyItem', () => {
     expect(container).toMatchSnapshot()
   })
 })
+
+describe('SelectCreateItem', () => {
+  it('should render correctly', () => {
+    const { container } = render(<SelectCreateItem />)
+    expect(container).toMatchSnapshot()
+  })
+})
diff --git a/src/components/Select/SelectMenu/__snapshots__/SelectMenuItem.test.tsx.snap b/src/components/Select/SelectMenu/__snapshots__/SelectMenuItem.test.tsx.snap
index 18f0b5a14..84c14bd25 100644
--- a/src/components/Select/SelectMenu/__snapshots__/SelectMenuItem.test.tsx.snap
+++ b/src/components/Select/SelectMenu/__snapshots__/SelectMenuItem.test.tsx.snap
@@ -1,5 +1,45 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`SelectCreateItem should render correctly 1`] = `
+.emotion-0 {
+  font-family: "IBM Plex Sans",sans-serif;
+  font-size: 0.8125rem;
+  line-height: 1.5;
+  color: #3A3A47;
+  border-bottom: 1px solid #D3D4DD;
+  cursor: pointer;
+  padding: 0.5rem 0.5rem;
+  -webkit-transition: .1s ease;
+  transition: .1s ease;
+  background: #F0F0F5;
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+}
+
+.emotion-0:last-of-type {
+  border-bottom: none;
+}
+
+.emotion-0:hover {
+  background: #F0F0F5;
+}
+
+.emotion-0:focus {
+  outline: 0;
+  border-radius: 3px;
+  box-shadow: inset 0 0 0 2px #0069D0;
+}
+
+<div>
+  <li
+    aria-selected="false"
+    class="emotion-0"
+  >
+    Select an item or type a new value
+  </li>
+</div>
+`;
+
 exports[`SelectEmptyItem should render correctly 1`] = `
 .emotion-0 {
   font-family: "IBM Plex Sans",sans-serif;
@@ -12,6 +52,8 @@ exports[`SelectEmptyItem should render correctly 1`] = `
   -webkit-transition: .1s ease;
   transition: .1s ease;
   background: #F0F0F5;
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
 }
 
 .emotion-0:last-of-type {
@@ -101,6 +143,8 @@ exports[`SelectLoadingItem should render correctly 1`] = `
   -webkit-transition: .1s ease;
   transition: .1s ease;
   background: #F0F0F5;
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
 }
 
 .emotion-1:last-of-type {
diff --git a/src/components/Select/SelectSingle/SelectSingle.test.tsx b/src/components/Select/SelectSingle/SelectSingle.test.tsx
index 4ca6a4702..e900c6030 100644
--- a/src/components/Select/SelectSingle/SelectSingle.test.tsx
+++ b/src/components/Select/SelectSingle/SelectSingle.test.tsx
@@ -1,6 +1,8 @@
 import { fireEvent, render } from '@testing-library/react'
 import React from 'react'
 
+import en from '../../../i18n/locales/en-US'
+
 import { DefaultItemType, SelectSingle, SelectSingleProps } from './SelectSingle'
 
 jest.mock('../../../util/string')
@@ -127,3 +129,39 @@ describe('input label', () => {
     expect(container.querySelector('input').getAttribute('aria-labelledby')).toBeFalsy()
   })
 })
+
+describe('createNewItem', () => {
+  it('should allow selectedItem to become whatever is typed on text input', () => {
+    const createNewItem = jest.fn(text => ({ value: -1, label: text }))
+    const change = jest.fn()
+    const { container } = render(<SelectTest id='foo' createNewItem={createNewItem} onChange={change} />)
+
+    fireEvent.change(container.querySelector('input'), { target: { value: 'my item' } })
+
+    expect(createNewItem).toHaveBeenCalledWith('my item')
+    expect(change).toHaveBeenCalledWith({ value: -1, label: 'my item' }, expect.anything())
+  })
+  it('should render CreateItemSelect message', () => {
+    const createNewItem = jest.fn(text => ({ value: -1, label: text }))
+    const { queryByText } = render(<SelectTest id='foo' createNewItem={createNewItem} isOpen />)
+    expect(queryByText(en.select.createItem)).toBeTruthy()
+  })
+  it('should NOT render CreateItemSelect message when suggestion list is empty', () => {
+    const createNewItem = jest.fn(text => ({ value: -1, label: text }))
+    const { container, queryByText } = render(<SelectTest id='foo' createNewItem={createNewItem} isOpen />)
+    fireEvent.change(container.querySelector('input'), { target: { value: 'empty suggestion' } })
+    expect(queryByText(en.select.createItem)).toBeFalsy()
+  })
+  it('should NOT open select when focus by default', () => {
+    const createNewItem = jest.fn(text => ({ value: -1, label: text }))
+    const { container } = render(<SelectTest id='foo' createNewItem={createNewItem} />)
+    fireEvent.focus(container.querySelector('input'))
+    expect(container.querySelector('ul')).toBeFalsy()
+  })
+  it('should open select when focus and openOnFocus is true', () => {
+    const createNewItem = jest.fn(text => ({ value: -1, label: text }))
+    const { container } = render(<SelectTest id='foo' createNewItem={createNewItem} openOnFocus />)
+    fireEvent.focus(container.querySelector('input'))
+    expect(container.querySelector('ul')).toBeTruthy()
+  })
+})
diff --git a/src/components/Select/SelectSingle/__snapshots__/SelectSingle.test.tsx.snap b/src/components/Select/SelectSingle/__snapshots__/SelectSingle.test.tsx.snap
index 9a14ba966..b3867f152 100644
--- a/src/components/Select/SelectSingle/__snapshots__/SelectSingle.test.tsx.snap
+++ b/src/components/Select/SelectSingle/__snapshots__/SelectSingle.test.tsx.snap
@@ -955,7 +955,7 @@ exports[`should render correctly when opened 1`] = `
                   width="1em"
                 >
                   <path
-                    d="M14.605 13.194l7 6.998a1 1 0 0 1-1.415 1.414l-6.997-7a7 7 0 1 1 1.412-1.412zM9 14A5 5 0 1 0 9 4a5 5 0 0 0 0 10z"
+                    d="M7.707 14.707a1 1 0 1 1-1.414-1.414l5-5a1 1 0 0 1 1.414 0l5.001 5.001a1 1 0 0 1-1.414 1.414L12 10.414l-4.293 4.293z"
                   />
                 </svg>
               </span>
diff --git a/src/components/SelectInline/__snapshots__/SelectInline.test.tsx.snap b/src/components/SelectInline/__snapshots__/SelectInline.test.tsx.snap
index f4d7af6ac..ea9b8f905 100644
--- a/src/components/SelectInline/__snapshots__/SelectInline.test.tsx.snap
+++ b/src/components/SelectInline/__snapshots__/SelectInline.test.tsx.snap
@@ -394,7 +394,7 @@ exports[`SelectInline should render correctly when closed 1`] = `
                       width="1em"
                     >
                       <path
-                        d="M14.605 13.194l7 6.998a1 1 0 0 1-1.415 1.414l-6.997-7a7 7 0 1 1 1.412-1.412zM9 14A5 5 0 1 0 9 4a5 5 0 0 0 0 10z"
+                        d="M7.707 14.707a1 1 0 1 1-1.414-1.414l5-5a1 1 0 0 1 1.414 0l5.001 5.001a1 1 0 0 1-1.414 1.414L12 10.414l-4.293 4.293z"
                       />
                     </svg>
                   </span>
@@ -847,7 +847,7 @@ exports[`SelectInline should render correctly when opened 1`] = `
                         width="1em"
                       >
                         <path
-                          d="M14.605 13.194l7 6.998a1 1 0 0 1-1.415 1.414l-6.997-7a7 7 0 1 1 1.412-1.412zM9 14A5 5 0 1 0 9 4a5 5 0 0 0 0 10z"
+                          d="M7.707 14.707a1 1 0 1 1-1.414-1.414l5-5a1 1 0 0 1 1.414 0l5.001 5.001a1 1 0 0 1-1.414 1.414L12 10.414l-4.293 4.293z"
                         />
                       </svg>
                     </span>
@@ -1301,7 +1301,7 @@ exports[`SelectInline should render correctly with button placeholder 1`] = `
                       width="1em"
                     >
                       <path
-                        d="M14.605 13.194l7 6.998a1 1 0 0 1-1.415 1.414l-6.997-7a7 7 0 1 1 1.412-1.412zM9 14A5 5 0 1 0 9 4a5 5 0 0 0 0 10z"
+                        d="M7.707 14.707a1 1 0 1 1-1.414-1.414l5-5a1 1 0 0 1 1.414 0l5.001 5.001a1 1 0 0 1-1.414 1.414L12 10.414l-4.293 4.293z"
                       />
                     </svg>
                   </span>