google-typeahead
Version:
A Google Search styled typeahead for React
247 lines (228 loc) • 8.4 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { useEffect, useState } from "react";
import PropTypes from 'prop-types';
import ClickAwayListener from "./ListenClickAway/ClickAwayListener";
import "./index.css";
import styled from "styled-components";
const Base = styled.div`color: rgba(0, 0, 0, 0.87);transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;background-color: #fff;box-shadow: 0 1px 6px 0 rgba(32, 33, 36, 0.28);position: relative;${props => {
if (props.open) {
return `&:after {
width: 100%;
height: 15px;
display: block;
background-color: white;
content: " ";
position: absolute;
bottom: -2px;
z-index: ${props.zIndex ? typeof props.zIndex === "string" ? parseInt(props.zIndex) + 1 : props.zIndex + 1 : 2};}border-top-left-radius:22px; border-top-right-radius:22px;`;
} else {
return `border-radius:22px`;
}
}}`;
export default function GoogleTypeahead(props) {
const {
zIndex,
id,
canCreate,
limit,
className,
options,
value: search,
setValue,
isCaseSensitive,
defaultOpen,
emptyLabel,
LeftSideComponent,
RightSideComponent,
onClick,
onChange,
onKeyDown,
...remainingProps
} = props;
if (options.length > 0 && typeof options[0] === "object" && typeof id !== "string") {
throw new Error("If options are an object, you must specify a string key to filter by as a prop named 'id'");
}
const filterOptions = value => {
if (options.length > 0 && typeof options[0] === "object") {
if (isCaseSensitive) {
return limit ? options.filter(opt => opt[id].indexOf(value) !== -1).slice(0, limit) : options.filter(opt => opt[id].indexOf(value) !== -1);
} else {
return limit ? options.filter(opt => opt[id].toLowerCase().indexOf(value.toLowerCase()) !== -1).slice(0, limit) : options.filter(opt => opt[id].toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
} else {
if (isCaseSensitive) {
return limit ? options.filter(opt => opt.indexOf(value) !== -1).slice(0, limit) : options.filter(opt => opt.indexOf(value) !== -1);
} else {
return limit ? options.filter(opt => opt.toLowerCase().indexOf(value.toLowerCase()) !== -1).slice(0, limit) : options.filter(opt => opt.toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
}
};
const [open, setOpen] = useState(Boolean(defaultOpen));
const [offset, setOffset] = useState(0);
const [remainingOptions, setRemainingOptions] = useState(filterOptions(search));
useEffect(() => {
if (search.length > 0) {
refreshSuggestions();
}
}, []);
const refreshSuggestions = value => {
setRemainingOptions(filterOptions(value));
};
const handleEnter = e => {
if (remainingOptions.length > 0) {
setOpen(false);
e.target.blur();
setValue(remainingOptions[offset]);
refreshSuggestions(remainingOptions[offset]);
setOffset(0);
} else if (canCreate) {
setOpen(false);
e.target.blur();
}
};
const handleClickAway = () => {
setOpen(false);
setOffset(0);
};
const handleClick = option => {
setValue(option);
refreshSuggestions(option);
setOpen(false);
setOffset(0);
};
const handleKeyDown = e => {
if (e.keyCode === 13) {
handleEnter(e);
setOffset(0);
} else if (e.keyCode === 38) {
e.preventDefault();
if (offset > 0) {
setOffset(offset - 1);
setValue(remainingOptions[offset - 1]);
}
} else if (e.keyCode === 40) {
if (offset < remainingOptions.length - 1) {
setOffset(offset + 1);
setValue(remainingOptions[offset + 1]);
}
}
if (onKeyDown) {
onKeyDown(e);
}
};
const handleOnClick = e => {
setOpen(true);
if (onClick) {
onClick(e);
}
};
const handleChange = e => {
refreshSuggestions(e.target.value);
setOffset(0);
setValue(e.target.value);
if (onChange) {
onChange(e);
}
};
return /*#__PURE__*/React.createElement("div", {
style: {
display: "flex",
justifyContent: "center",
marginTop: "1rem"
}
}, /*#__PURE__*/React.createElement(ClickAwayListener, {
onClickAway: () => handleClickAway()
}, /*#__PURE__*/React.createElement(Base, {
className: className ? className : undefined,
open: open,
zIndex: zIndex
}, /*#__PURE__*/React.createElement("div", {
style: {
display: "flex",
justifyContent: "center",
alignItems: "center",
paddingLeft: "1rem",
paddingRight: "1rem"
}
}, LeftSideComponent && /*#__PURE__*/React.createElement(LeftSideComponent, null), /*#__PURE__*/React.createElement("input", _extends({
onClick: () => handleOnClick(),
className: "google-typeahead-input",
onKeyDown: e => handleKeyDown(e),
onChange: e => handleChange(e),
value: search
}, remainingProps)), RightSideComponent && /*#__PURE__*/React.createElement(RightSideComponent, null)), /*#__PURE__*/React.createElement("div", {
className: "google-typeahead-dropdown",
style: {
zIndex: zIndex ? zIndex : "1",
paddingTop: open ? "2px" : "0px"
}
}, open && (remainingOptions.length === 0 ? canCreate ? /*#__PURE__*/React.createElement("li", {
className: "google-typeahead-item google-typeahead-selected",
style: {
borderBottomRightRadius: "22px",
borderBottomLeftRadius: "22px"
}
}, /*#__PURE__*/React.createElement("div", {
className: "google-typeahead-text"
}, /*#__PURE__*/React.createElement("b", null, search))) : /*#__PURE__*/React.createElement("li", {
className: "google-typeahead-item",
style: {
borderBottomRightRadius: "22px",
borderBottomLeftRadius: "22px"
}
}, /*#__PURE__*/React.createElement("div", {
className: "google-typeahead-text"
}, emptyLabel ? emptyLabel : "No Items Found")) : remainingOptions.map((opt, index) => {
return /*#__PURE__*/React.createElement("li", {
className: index === offset ? "google-typeahead-item google-typeahead-selected" : "google-typeahead-item",
key: index,
onClick: () => handleClick(opt),
style: remainingOptions.length - 1 === index ? {
borderBottomRightRadius: "22px",
borderBottomLeftRadius: "22px"
} : undefined
}, /*#__PURE__*/React.createElement("div", {
className: "google-typeahead-text"
}, /*#__PURE__*/React.createElement(BoldedText, {
text: typeof remainingOptions[0] === "object" ? opt[id] : opt,
shouldBeBold: search,
isCaseSensitive: isCaseSensitive
}), " "));
}))))));
}
function BoldedText({
text,
shouldBeBold,
isCaseSensitive
}) {
const textArray = isCaseSensitive ? text.split(shouldBeBold) : text.toLowerCase().split(shouldBeBold.toLowerCase());
var count = 0;
return /*#__PURE__*/React.createElement("span", null, textArray.map((item, index) => {
count += index === 0 ? item.length : item.length + shouldBeBold.length;
return /*#__PURE__*/React.createElement(Wrapper, {
key: index
}, text.substring(count - item.length, count), index !== textArray.length - 1 && /*#__PURE__*/React.createElement("b", null, isCaseSensitive ? shouldBeBold : text.substring(count, count + shouldBeBold.length)));
}));
}
const Wrapper = ({
children
}) => /*#__PURE__*/React.createElement(React.Fragment, null, children);
GoogleTypeahead.propTypes = {
options: PropTypes.array.isRequired,
setValue: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
canCreate: PropTypes.bool,
isCaseSensitive: PropTypes.bool,
defaultOpen: PropTypes.bool,
className: PropTypes.string,
onClick: PropTypes.func,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onKeyDown: PropTypes.func,
limit: PropTypes.number,
id: PropTypes.string,
emptyLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
LeftSideComponent: PropTypes.elementType,
RightSideComponent: PropTypes.elementType
};