Last active
March 10, 2023 18:35
-
-
Save hongz1/8368c79690b8bdfe5a86f686e3257134 to your computer and use it in GitHub Desktop.
MUIv5 + React18 compatible version of material-ui-search-bar.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable react/react-in-jsx-scope -- Unaware of jsxImportSource */ | |
/** @jsxImportSource @emotion/react */ | |
import React from "react"; | |
import styled from "@emotion/styled"; | |
import { css } from "@emotion/react"; | |
import PropTypes from "prop-types"; | |
import { IconButton as MuiIconButton, Input, Paper as MuiPaper } from "@mui/material"; | |
import { Clear as ClearIcon, Search as SearchIcon } from "@mui/icons-material"; | |
const Paper = styled(MuiPaper)` | |
height: ${(props) => props.theme.spacing(6)}; | |
display: flex; | |
justify-content: space-between; | |
`; | |
const SearchInput = styled(Input)` | |
width: 100%; | |
`; | |
const SearchContainer = styled.div` | |
margin: auto ${(props) => props.theme.spacing(4)}; | |
width: calc(100% - ${(props) => props.theme.spacing(6 + 4)}); | |
`; | |
const IconButton = styled(MuiIconButton)` | |
color: ${(props) => props.theme.palette.action.active}; | |
transform: scale(1, 1); | |
transition: ${(props) => | |
props.theme.transitions.create(["transform", "color"], { | |
duration: props.theme.transitions.duration.shorter, | |
easing: props.theme.transitions.easing.easeInOut, | |
})}; | |
`; | |
const SearchIconButton = styled(IconButton)` | |
margin-right: ${(props) => props.theme.spacing(-8)}; | |
`; | |
const iconButtonHidden = css` | |
transform: scale(0, 0); | |
"& > $icon": { | |
opacity: 0; | |
} | |
`; | |
const iconTransition = { | |
transition: (theme) => | |
theme.transitions.create(["opacity"], { | |
duration: theme.transitions.duration.shorter, | |
easing: theme.transitions.easing.easeInOut, | |
}), | |
}; | |
/** | |
* Material design search bar | |
* @see [Search patterns](https://material.io/archive/guidelines/patterns/search.html) | |
*/ | |
const SearchBar = React.forwardRef( | |
( | |
{ cancelOnEscape, className, classes, closeIcon, disabled, onCancelSearch, onRequestSearch, searchIcon, style, ...inputProps }, | |
ref | |
) => { | |
const inputRef = React.useRef(); | |
const [value, setValue] = React.useState(inputProps.value); | |
React.useEffect(() => { | |
setValue(inputProps.value); | |
}, [inputProps.value]); | |
const handleFocus = React.useCallback( | |
(e) => { | |
if (inputProps.onFocus) { | |
inputProps.onFocus(e); | |
} | |
}, // eslint-disable-next-line react-hooks/exhaustive-deps | |
[inputProps.onFocus] | |
); | |
const handleBlur = React.useCallback( | |
(e) => { | |
setValue((v) => v.trim()); | |
if (inputProps.onBlur) { | |
inputProps.onBlur(e); | |
} | |
}, // eslint-disable-next-line react-hooks/exhaustive-deps | |
[inputProps.onBlur] | |
); | |
const handleInput = React.useCallback( | |
(e) => { | |
setValue(e.target.value); | |
if (inputProps.onChange) { | |
inputProps.onChange(e.target.value); | |
} | |
}, // eslint-disable-next-line react-hooks/exhaustive-deps | |
[inputProps.onChange] | |
); | |
const handleCancel = React.useCallback(() => { | |
setValue(""); | |
if (onCancelSearch) { | |
onCancelSearch(); | |
} | |
}, [onCancelSearch]); | |
const handleRequestSearch = React.useCallback(() => { | |
if (onRequestSearch) { | |
onRequestSearch(value); | |
} | |
}, [onRequestSearch, value]); | |
const handleKeyUp = React.useCallback( | |
(e) => { | |
if (e.charCode === 13 || e.key === "Enter") { | |
handleRequestSearch(); | |
} else if (cancelOnEscape && (e.charCode === 27 || e.key === "Escape")) { | |
handleCancel(); | |
} | |
if (inputProps.onKeyUp) { | |
inputProps.onKeyUp(e); | |
} | |
}, // eslint-disable-next-line react-hooks/exhaustive-deps | |
[handleRequestSearch, cancelOnEscape, handleCancel, inputProps.onKeyUp] | |
); | |
React.useImperativeHandle(ref, () => ({ | |
focus: () => { | |
inputRef.current.focus(); | |
}, | |
blur: () => { | |
inputRef.current.blur(); | |
}, | |
})); | |
return ( | |
<Paper className={className} style={style}> | |
<SearchContainer> | |
<SearchInput | |
{...inputProps} | |
inputRef={inputRef} | |
onBlur={handleBlur} | |
value={value} | |
onChange={handleInput} | |
onKeyUp={handleKeyUp} | |
onFocus={handleFocus} | |
fullWidth | |
disableUnderline | |
disabled={disabled} | |
/> | |
</SearchContainer> | |
<SearchIconButton | |
onClick={handleRequestSearch} | |
css={value !== "" ? iconButtonHidden : null} | |
disabled={disabled} | |
aria-label="btn-search-request" | |
> | |
{React.cloneElement(searchIcon, { sx: iconTransition })} | |
</SearchIconButton> | |
<IconButton | |
onClick={handleCancel} | |
css={value === "" ? iconButtonHidden : null} | |
disabled={disabled} | |
aria-label="btn-search-cancel" | |
> | |
{React.cloneElement(closeIcon, { sx: iconTransition })} | |
</IconButton> | |
</Paper> | |
); | |
} | |
); | |
SearchBar.defaultProps = { | |
className: "", | |
closeIcon: <ClearIcon />, | |
disabled: false, | |
placeholder: "Search", | |
searchIcon: <SearchIcon />, | |
style: null, | |
value: "", | |
}; | |
SearchBar.propTypes = { | |
/** Whether to clear search on escape */ | |
cancelOnEscape: PropTypes.bool, | |
/** Override or extend the styles applied to the component. */ | |
classes: PropTypes.object, | |
/** Custom top-level class */ | |
className: PropTypes.string, | |
/** Override the close icon. */ | |
closeIcon: PropTypes.node, | |
/** Disables text field. */ | |
disabled: PropTypes.bool, | |
/** Fired when the search is cancelled. */ | |
onCancelSearch: PropTypes.func, | |
/** Fired when the text value changes. */ | |
onChange: PropTypes.func, | |
/** Fired when the search icon is clicked. */ | |
onRequestSearch: PropTypes.func, | |
/** Sets placeholder text for the embedded text field. */ | |
placeholder: PropTypes.string, | |
/** Override the search icon. */ | |
searchIcon: PropTypes.node, | |
/** Override the inline-styles of the root element. */ | |
style: PropTypes.object, | |
/** The value of the text field. */ | |
value: PropTypes.string, | |
}; | |
export default SearchBar; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment