Skip to content

Commit

Permalink
add custom autocomplete using mui and downshift
Browse files Browse the repository at this point in the history
  • Loading branch information
paulclindo committed Oct 15, 2021
1 parent 19426f6 commit ab3f378
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 49 deletions.
156 changes: 113 additions & 43 deletions packages/yoroi-extension/app/components/common/Autocomplete.js
@@ -1,33 +1,36 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable react/no-array-index-key */
// @flow
import { useState } from 'react';
import { useRef, useState } from 'react';
import type { Node } from 'react';
import { useCombobox, useMultipleSelection } from 'downshift';
import { Input, Box, InputLabel, FormControl, FormHelperText, Chip } from '@mui/material';
import { Input, Box, InputLabel, FormControl, FormHelperText, Chip, useTheme } from '@mui/material';
import { styled } from '@mui/system';
import { slice } from 'lodash';
import SuccessIcon from '../../assets/images/forms/done.inline.svg';
import ErrorIcon from '../../assets/images/forms/error.inline.svg';
import CloseIcon from '../../assets/images/close-chip.inline.svg';

type Props = {|
+options: Array<any>,
+maxSelections: number,
+done: boolean,
+disabled: boolean,
+error: ?boolean,
+maxVisibleOptions: number,
+noResultsMessage: string,
+options: Array<string>,
+maxSelections?: number,
+done?: boolean,
+disabled?: boolean,
+error?: boolean,
+maxVisibleOptions?: number,
+noResultsMessage?: string,
+id: string,
+placeholder: string,
+label: string,
+preselectedOptions: Array<any>,
+onChange: () => void,
+value: Array<any>,
+onChange: (Array<string>) => void,
+value: Array<string>,
+autoFocus?: boolean,
+type: string,
+name: string,
+chipProps?: Object,
|};

export default function Autocomplete({
function Autocomplete({
options,
maxSelections,
done,
Expand All @@ -37,12 +40,16 @@ export default function Autocomplete({
label,
disabled,
id,
// onChange,
// value: selectedItems,
onChange,
value,
autoFocus,
type,
name,
placeholder,
chipProps,
}: Props): Node {
const [inputValue, setInputValue] = useState<string>('');

const inputRef = useRef();
const filteredList = options.filter(
item => !inputValue || item.toLowerCase().includes(inputValue.toLowerCase())
);
Expand All @@ -53,8 +60,11 @@ export default function Autocomplete({
getDropdownProps,
addSelectedItem,
removeSelectedItem,
selectedItems,
} = useMultipleSelection();
} = useMultipleSelection({
onSelectedItemsChange: ({ selectedItems }) => {
onChange(selectedItems);
},
});

const {
isOpen,
Expand All @@ -64,15 +74,15 @@ export default function Autocomplete({
getComboboxProps,
highlightedIndex,
getItemProps,
toggleMenu,
openMenu,
} = useCombobox({
inputValue,
defaultHighlightedIndex: 0,
selectedItem: null,
items: filteredList,
stateReducer: (state, actionAndChanges) => {
const { changes, type } = actionAndChanges;
switch (type) {
const { changes, type: actionType } = actionAndChanges;
switch (actionType) {
case useCombobox.stateChangeTypes.InputKeyDownEnter:
case useCombobox.stateChangeTypes.ItemClick:
case useCombobox.stateChangeTypes.InputChange:
Expand All @@ -93,7 +103,8 @@ export default function Autocomplete({
case useCombobox.stateChangeTypes.InputKeyDownEnter:
case useCombobox.stateChangeTypes.ItemClick:
case useCombobox.stateChangeTypes.InputBlur:
if (selectedItem && selectedItems.length < maxSelections) {
// $FlowFixMe[invalid-compare]
if (selectedItem && value.length < maxSelections) {
setInputValue('');
addSelectedItem(selectedItem);
}
Expand All @@ -103,14 +114,33 @@ export default function Autocomplete({
}
},
});

const theme = useTheme();
return (
<StyledFormControl error={Boolean(error)} disabled={disabled} fullWidth>
<InputLabel htmlFor={id ?? 'autocomplete-combobox'} {...getLabelProps()}>
<StyledFormControl
error={Boolean(error)}
// $FlowFixMe[invalid-compare]
disabled={disabled === true || value.length >= maxSelections}
fullWidth
>
<InputLabel
sx={{ padding: '0 6px', background: 'white' }}
{...(value.length || theme.name === 'classic' ? { shrink: true } : {})}
htmlFor={id ?? 'autocomplete-combobox'}
{...getLabelProps()}
>
{label}
</InputLabel>
<InputWrapper onClick={toggleMenu} error={error} isOpen={isOpen}>
{selectedItems.map((selectedItem, index) => (
<InputWrapper
onClick={() => {
if (!isOpen) {
inputRef.current?.focus();
openMenu();
}
}}
error={error}
isOpen={isOpen}
>
{value.map((selectedItem, index) => (
<Chip
variant="autocomplete"
key={`selected-item-${index}`}
Expand All @@ -119,26 +149,37 @@ export default function Autocomplete({
removeSelectedItem(selectedItem);
}}
deleteIcon={<CloseIcon />}
{...chipProps}
{...getSelectedItemProps({ selectedItem, index })}
/>
))}
<Box sx={{ flex: 1 }} {...getComboboxProps()}>
<Box {...getComboboxProps()}>
<Input
placeholder={selectedItems.length >= maxSelections ? '' : placeholder}
disabled={selectedItems.length >= maxSelections}
color="input"
// $FlowFixMe[invalid-compare]
placeholder={value.length >= maxSelections ? '' : placeholder}
// $FlowFixMe[invalid-compare]
disabled={value.length >= maxSelections}
disableUnderline
fullWidth
autoFocus={autoFocus}
error={Boolean(error)}
id={id ?? 'autocomplete-combobox'}
{...getInputProps(getDropdownProps({ preventKeyAction: isOpen }))}
{...getInputProps({
inputRef,
type,
name,
autoFocus,
...getDropdownProps(),
})}
/>
</Box>
<Box display="flex" alignItems="center">
<CheckWrapper>
{done === true ? <SuccessIcon /> : null}
{Boolean(error) === true ? <ErrorIcon /> : null}
</Box>
</CheckWrapper>
</InputWrapper>
<FormHelperText sx={{ marginLeft: 0 }} id={id ?? 'autocomplete-combobox'}>
<FormHelperText sx={{ position: 'absolute', bottom: 0 }} id={id ?? 'autocomplete-combobox'}>
{error}
</FormHelperText>

Expand Down Expand Up @@ -183,11 +224,40 @@ export default function Autocomplete({
</StyledFormControl>
);
}
export default Autocomplete;

Autocomplete.defaultProps = {
maxSelections: 10,
done: false,
disabled: false,
error: false,
autoFocus: false,
maxVisibleOptions: 10,
noResultsMessage: '',
chipProps: null,
};

const StyledFormControl = styled(FormControl)({
marginTop: '23px',
paddingBottom: '12px',
color: 'hsl(237 37% 11%)',
marginTop: '10px',
marginBottom: '10px',
paddingBottom: '24px',
'& .MuiInputLabel-root': {
'&.MuiInputLabel-shrink ': {
color: '#2a2b32',
},
'&.Mui-error': {
color: 'red',
},
},
});

const CheckWrapper = styled(Box)({
display: 'flex',
minWidth: '35px',
position: 'absolute',
right: 0,
top: '50%',
transform: 'translateY(-50%)',
});

const ULList = styled(Box)({
Expand All @@ -198,14 +268,15 @@ const ULList = styled(Box)({
position: 'absolute',
zIndex: 1000,
left: 0,
top: '87%',
top: '100%',
maxHeight: '30rem',
overflowY: 'auto',
overflowX: 'hidden',
outline: '0',
transition: 'opacity .1s ease',
borderRadius: 0,
});

const InputWrapper = styled('div')(
({ theme, error, isOpen }) => `
width: 100%;
Expand All @@ -223,21 +294,20 @@ const InputWrapper = styled('div')(
min-height: ${theme.name === 'classic' ? '73px' : '140px'};
align-content: baseline;
display: inline-flex;
padding: 0 10px 10px 2px;
padding: 10px;
padding-right: 40px;
padding-left: 5px;
flex-wrap: wrap;
position: relative;
& input {
background-color: transparent;
color: ${theme.name === 'classic' ? 'hsl(237 37% 11%)' : 'rgba(0,0,0,.85)'};
height: 30px;
color: hsl(225deg 4% 30%);
font-weight: 300;
font-size: 0.9rem;
padding: 4px 6px;
letter-spacing: 0;
border: 0;
margin: 0;
outline: 0;
}
`
);
12 changes: 6 additions & 6 deletions packages/yoroi-extension/app/styles/overrides/Chip.js
Expand Up @@ -59,11 +59,11 @@ const ClassicChip = {
margin: '5px 3px 0 3px',
textTransform: 'lowercase',
padding: '3px 0 3px 6px',
background: 'hsl(9deg 46% 73%)',
background: '#daa49a',
height: '28px',
display: 'flex',
alignItems: 'center',
color: 'hsl(210deg 25% 98%)',
color: '#f9fafb',
fontSize: '0.9rem',
fontWeight: 300,
span: {
Expand All @@ -75,7 +75,7 @@ const ClassicChip = {
color: 'inherit',
},
'&:hover': {
background: 'hsl(9deg 46% 73%)',
background: '#daa49a',
},
},
},
Expand Down Expand Up @@ -142,13 +142,13 @@ const ModernChip = {
textTransform: 'lowercase',
margin: '5px 3px 0 3px',
padding: '3px 0 4px 3px',
background: 'hsl(204deg 20% 95%)',
background: '#f0f3f5',
height: '28px',
display: 'flex',
alignItems: 'center',
fontSize: '0.875rem',
fontWeight: 300,
color: 'hsl(0deg 0% 21%)',
color: '#363636',
letterSpacing: 0,
span: {
padding: 0,
Expand All @@ -159,7 +159,7 @@ const ModernChip = {
color: 'inherit',
},
'&:hover': {
background: 'hsl(204deg 20% 95%)',
background: '#f0f3f5',
},
},
},
Expand Down

0 comments on commit ab3f378

Please sign in to comment.