├── .babelrc ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── SECURITY.md ├── dist └── index.min.js ├── docs ├── bundle.min.js ├── index.html └── react-search-field.png ├── jest.config.js ├── package.json ├── src ├── components │ └── SearchField.jsx └── docs │ ├── App.css │ ├── App.jsx │ ├── exampleSnippets.js │ ├── index.html │ ├── index.jsx │ └── react-search-field.png ├── tests ├── SearchField.test.jsx └── test.setup.js ├── webpack.config.js ├── webpack.production.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties" 8 | ] 9 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaFeatures": { 6 | "jsx": true, 7 | "modules": true 8 | } 9 | }, 10 | "env": { 11 | "browser": true, 12 | "es6": true, 13 | "jest": true 14 | }, 15 | "plugins": [ 16 | "react" 17 | ], 18 | "rules": { 19 | "import/no-extraneous-dependencies": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: nutboltu 4 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Package CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm run test 29 | - run: npm run build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .vscode 3 | .idea 4 | 5 | # Test coverage 6 | coverage 7 | .nyc_output 8 | 9 | # Dependency directories 10 | node_modules 11 | obj 12 | typings 13 | 14 | # Logs 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Lock files 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | .idea 4 | src/ 5 | docs/ 6 | test/ 7 | README.md 8 | jest.config.js 9 | LICENSE 10 | SECURITY.md 11 | .babelrc 12 | .gitignore 13 | .eslintignore 14 | .eslintrc 15 | webpack.config.js 16 | webpack.production.config.js 17 | yarn.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Farhad Yasir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Search Field 2 | 3 | [![dependencies Status](https://david-dm.org/nutboltu/react-search-field/status.svg)](https://david-dm.org/nutboltu/react-search-field) [![Actions Status](https://github.com/nutboltu/react-search-field/workflows/Package%20CI/badge.svg)](https://github.com/nutboltu/react-search-field/actions) 4 | 5 | Follow @nutboltu 6 | 7 | 8 | [![NPM](https://nodei.co/npm/react-search-field.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/react-search-field/) 9 | 10 | An elegant search field component for React. 11 | 12 | See [Demo and Documentation]( https://nutboltu.github.io/react-search-field/). 13 | 14 | ![Screenshot](/docs/react-search-field.png) 15 | 16 | ## Props 17 | 18 | The component takes the following props. 19 | 20 | | Prop | Type | Description | 21 | |-------------------|------------|-------------| 22 | | `classNames` | string | Additional classnames for the component | 23 | | `searchText` | string | Initial search value of the input | 24 | | `placeholder` | string | placeholder for the search input | 25 | | `disabled` | boolean | Disabling the search input | 26 | | `onChange` | _function_ | Callback function to invoke when the user press any key. The function should contain two parameters(value, event). | 27 | | `onEnter` | _function_ | Callback function to invoke when the user press enter after pressing few keys. The function should contain two parameters(value, event). | 28 | | `onSearchClick` | _function_ | Callback function to invoke when the user click the search button. The function should contain one parameter(value). | 29 | | `onBlur` | _function_ | Callback function to invoke when the user blurs the search box. The function should contain two parameters(value, event). | 30 | 31 | ## Installation 32 | 33 | ```bash 34 | npm install react-search-field --save 35 | ``` 36 | 37 | ## Usage 38 | 39 | ```javascript 40 | import SearchField from "react-search-field"; 41 | 42 | 48 | ``` 49 | 50 | ## Run 51 | 52 | ```bash 53 | npm start 54 | ``` 55 | 56 | ## License 57 | 58 | MIT Licensed. Copyright (c) Farhad Yasir 2021. 59 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /dist/index.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactSearch=t(require("react")):e.ReactSearch=t(e.React)}(this,(function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(t,n){t.exports=e},function(e,t,n){e.exports=n(4)()},function(e,t,n){"use strict";const r=e=>Object.prototype.toString.call(e).slice(8,-1),o=e=>"Array"===r(e),a=e=>"Object"===r(e),c=e=>"String"===r(e),i=e=>"Number"===r(e)&&!Number.isNaN(e),l=e=>"Null"===r(e)||"Undefined"===r(e),u=e=>c(e)?""===e.trim():o(e)?0===e.length:a(e)?0===Object.keys(e).length:l(e);e.exports={isArray:o,isObject:a,isString:c,isDate:e=>"Date"===r(e),isRegExp:e=>"RegExp"===r(e),isFunction:e=>"Function"===r(e),isBoolean:e=>"Boolean"===r(e)||c(e)&&("true"===e||"false"===e),isNumber:i,isNull:l,isEmpty:u,isEmptyOrZero:e=>u(e)||i(e)&&0===e}},function(e,t,n){"use strict";n.r(t);var r=n(0),o=n.n(r),a=n(1),c=n.n(a),i=n(2),l=n.n(i);var u={border:"1px #ddd solid",display:"inline-flex",justifyContent:"space-between",height:35},s=function(e){return{height:33,width:33,outline:"none",backgroundColor:"white",cursor:e?"auto":"pointer",padding:5,boxSizing:"border-box",appearance:"none",border:"none",borderLeft:"1px #ddd solid"}},f={outline:"none",border:"none",fontSize:14,padding:"0 8px",flex:1,color:"#5a5a5a",fontWeight:100,height:33},p=function(){var e=Math.ceil(21);return o.a.createElement("svg",{version:"1.1",x:"0px",y:"0px",width:e,height:e,viewBox:"0 0 635 635",style:{fill:"#727272"}},o.a.createElement("g",null,o.a.createElement("path",{d:"M255.108,0C119.863,0,10.204,109.66,10.204,244.904c0,135.245,109.659,244.905,244.904,244.905 c52.006,0,100.238-16.223,139.883-43.854l185.205,185.176c1.671,1.672,4.379,1.672,5.964,0.115l34.892-34.891 c1.613-1.613,1.47-4.379-0.115-5.965L438.151,407.605c38.493-43.246,61.86-100.237,61.86-162.702 C500.012,109.66,390.353,0,255.108,0z M255.108,460.996c-119.34,0-216.092-96.752-216.092-216.092 c0-119.34,96.751-216.091,216.092-216.091s216.091,96.751,216.091,216.091C471.199,364.244,374.448,460.996,255.108,460.996z"})))},d=function(e){var t=e.classNames,n=e.searchText,a=e.placeholder,c=e.disabled,i=e.onChange,d=e.onEnter,b=e.onSearchClick,y=e.onBlur,h=function(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)){var n=[],r=!0,o=!1,a=void 0;try{for(var c,i=e[Symbol.iterator]();!(r=(c=i.next()).done)&&(n.push(c.value),!t||n.length!==t);r=!0);}catch(e){o=!0,a=e}finally{try{r||null==i.return||i.return()}finally{if(o)throw a}}return n}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}(Object(r.useState)(n),2),g=h[0],m=h[1];Object(r.useEffect)((function(){m(n)}),[n,m]);var x=Object(r.useCallback)((function(e){m(e.target.value),l.a.isFunction(i)&&i(e.target.value,e)}),[i,m]),v=Object(r.useCallback)((function(e){(13===e.which||13===e.keyCode)&&l.a.isFunction(d)&&d(e.target.value,e)}),[d]),O=Object(r.useCallback)((function(){l.a.isFunction(b)&&b(g)}),[b,g]),j=Object(r.useCallback)((function(e){l.a.isFunction(y)&&y(e.target.value,e)}),[y]),S="react-search-field ".concat(t);return o.a.createElement("div",{className:S,style:u},o.a.createElement("input",{className:"react-search-field-input",style:f,onChange:x,onKeyPress:v,onBlur:j,placeholder:a,type:"text",value:g,disabled:c}),o.a.createElement("button",{className:"react-search-field-button",type:"button","aria-label":"search button",style:s(c),onClick:O,disabled:c},o.a.createElement(p,null)))};d.propTypes={classNames:c.a.string,searchText:c.a.string,placeholder:c.a.string,disabled:c.a.bool,onChange:c.a.func,onEnter:c.a.func,onSearchClick:c.a.func,onBlur:c.a.func},d.defaultProps={classNames:"",searchText:"",placeholder:"Search",disabled:!1,onChange:null,onEnter:null,onSearchClick:null,onBlur:null},t.default=d},function(e,t,n){"use strict";var r=n(5);function o(){}function a(){}a.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,a,c){if(c!==r){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:a,resetWarningCache:o};return n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"}])})); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Search Field 7 | 8 | 9 | 10 | 11 | Fork me on GitHub 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /docs/react-search-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nutboltu/react-search-field/51e8856f48edf8146f68b1ccc863ae4d368915b1/docs/react-search-field.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFilesAfterEnv: ['tests/test.setup.js'], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-search-field", 3 | "version": "2.0.1", 4 | "description": "This is a simple search field component for react", 5 | "main": "dist/index.min.js", 6 | "scripts": { 7 | "build:component": "rm -rf dist/ && webpack -p --progress --config=webpack.production.config.js", 8 | "build:docs": "rm -rf docs/ && webpack -p --progress", 9 | "build": "npm run build:component && npm run build:docs", 10 | "start": "npm run build:docs && webpack-dev-server", 11 | "test": "jest", 12 | "lint": "eslint src" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "react search", 17 | "react search field", 18 | "react search component", 19 | "searchbar", 20 | "search" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/nutboltu/react-search-field.git" 25 | }, 26 | "dependencies": { 27 | "prop-types": "15.7.2", 28 | "typeco": "1.0.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "7.6.4", 32 | "@babel/node": "7.6.3", 33 | "@babel/plugin-proposal-class-properties": "7.5.5", 34 | "@babel/preset-env": "7.6.3", 35 | "@babel/preset-react": "7.6.3", 36 | "babel-eslint": "10.1.0", 37 | "babel-loader": "8.0.6", 38 | "copy-webpack-plugin": "6.0.3", 39 | "css-loader": "3.2.0", 40 | "enzyme": "3.10.0", 41 | "enzyme-adapter-react-16": "1.15.1", 42 | "eslint": "6.5.1", 43 | "eslint-config-airbnb": "18.0.1", 44 | "eslint-plugin-import": "2.18.2", 45 | "eslint-plugin-jsx-a11y": "6.2.3", 46 | "eslint-plugin-react": "7.16.0", 47 | "html-webpack-plugin": "3.2.0", 48 | "jest": "^24.9.0", 49 | "react": "16.9.0", 50 | "react-dom": "16.9.0", 51 | "react-github-btn": "1.2.0", 52 | "react-syntax-highlighter": "13.5.1", 53 | "style-loader": "1.0.0", 54 | "terser-webpack-plugin": "4.1.0", 55 | "webpack": "4.41.1", 56 | "webpack-cli": "3.3.9", 57 | "webpack-dev-server": "3.11.0" 58 | }, 59 | "peerDependencies": { 60 | "react": ">=16.9.0", 61 | "react-dom": "16.9.0" 62 | }, 63 | "author": "Farhad Yasir", 64 | "license": "MIT" 65 | } 66 | -------------------------------------------------------------------------------- /src/components/SearchField.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import TypeChecker from 'typeco'; 4 | 5 | const ENTER_KEY = 13; 6 | const SEARCH_BUTTON_EDGE = 35; 7 | 8 | const searchFieldStyle = { 9 | border: '1px #ddd solid', 10 | display: 'inline-flex', 11 | justifyContent: 'space-between', 12 | height: SEARCH_BUTTON_EDGE, 13 | }; 14 | 15 | const searchFieldButtonStyle = (disabled) => ({ 16 | height: SEARCH_BUTTON_EDGE - 2, // reduces 2px because of top and bottom border 17 | width: SEARCH_BUTTON_EDGE - 2, 18 | outline: 'none', 19 | backgroundColor: 'white', 20 | cursor: disabled ? 'auto' : 'pointer', 21 | padding: 5, 22 | boxSizing: 'border-box', 23 | appearance: 'none', 24 | border: 'none', 25 | borderLeft: '1px #ddd solid', 26 | }); 27 | 28 | const searchFieldInputStyle = { 29 | outline: 'none', 30 | border: 'none', 31 | fontSize: 14, 32 | padding: '0 8px', 33 | flex: 1, 34 | color: '#5a5a5a', 35 | fontWeight: 100, 36 | height: SEARCH_BUTTON_EDGE - 2, 37 | }; 38 | 39 | const SearchIcon = () => { 40 | const iconEdge = Math.ceil(SEARCH_BUTTON_EDGE * 0.60); 41 | const searchIconStyle = { 42 | fill: '#727272', 43 | }; 44 | return ( 45 | 54 | 55 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | const SearchField = ({ 67 | classNames, 68 | searchText, 69 | placeholder, 70 | disabled, 71 | onChange, 72 | onEnter, 73 | onSearchClick, 74 | onBlur, 75 | }) => { 76 | const [value, setValue] = useState(searchText); 77 | 78 | useEffect(() => { 79 | setValue(searchText); 80 | }, [searchText, setValue]); 81 | 82 | const onChangeHandler = useCallback((event) => { 83 | setValue(event.target.value); 84 | if (TypeChecker.isFunction(onChange)) { 85 | onChange(event.target.value, event); 86 | } 87 | }, [onChange, setValue]); 88 | 89 | const onEnterHandler = useCallback((event) => { 90 | const isEnterPressed = event.which === ENTER_KEY 91 | || event.keyCode === ENTER_KEY; 92 | if (isEnterPressed && TypeChecker.isFunction(onEnter)) { 93 | onEnter(event.target.value, event); 94 | } 95 | }, [onEnter]); 96 | 97 | const onSearchClickHandler = useCallback(() => { 98 | if (TypeChecker.isFunction(onSearchClick)) { 99 | onSearchClick(value); 100 | } 101 | }, [onSearchClick, value]); 102 | 103 | const onBlurHandler = useCallback((event) => { 104 | if (TypeChecker.isFunction(onBlur)) { 105 | onBlur(event.target.value, event); 106 | } 107 | }, [onBlur]); 108 | 109 | const className = `react-search-field ${classNames}`; 110 | 111 | return ( 112 |
116 | 127 | 137 |
138 | ); 139 | }; 140 | 141 | SearchField.propTypes = { 142 | classNames: PropTypes.string, 143 | searchText: PropTypes.string, 144 | placeholder: PropTypes.string, 145 | disabled: PropTypes.bool, 146 | onChange: PropTypes.func, 147 | onEnter: PropTypes.func, 148 | onSearchClick: PropTypes.func, 149 | onBlur: PropTypes.func, 150 | }; 151 | 152 | SearchField.defaultProps = { 153 | classNames: '', 154 | searchText: '', 155 | placeholder: 'Search', 156 | disabled: false, 157 | onChange: null, 158 | onEnter: null, 159 | onSearchClick: null, 160 | onBlur: null, 161 | }; 162 | 163 | export default SearchField; 164 | -------------------------------------------------------------------------------- /src/docs/App.css: -------------------------------------------------------------------------------- 1 | .react-search-field-demo { 2 | padding: 20px; 3 | } 4 | 5 | h3, h5 { 6 | margin: 20px 0; 7 | } 8 | 9 | ul, li { 10 | list-style: none; 11 | margin: 0; 12 | font-size: 13px; 13 | width: 100%; 14 | } 15 | 16 | ul { 17 | display: inline-flex; 18 | padding: 10px; 19 | border-bottom: 1px solid #ddd; 20 | } 21 | 22 | .list-example { 23 | border: 1px solid #ddd; 24 | width: 400px; 25 | margin: 15px 0; 26 | display: table; 27 | } 28 | 29 | .list-body { 30 | display: flex; 31 | flex-direction: column; 32 | } 33 | 34 | .github-button { 35 | padding: 0 5px; 36 | display: inline-block; 37 | } -------------------------------------------------------------------------------- /src/docs/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import GitHubButton from 'react-github-btn'; 3 | import SyntaxHighlighter from 'react-syntax-highlighter'; 4 | import { github } from 'react-syntax-highlighter/dist/cjs/styles/hljs'; 5 | import TypeChecker from 'typeco'; 6 | 7 | import SearchField from '../components/SearchField'; 8 | import exampleSnippets from './exampleSnippets'; 9 | 10 | import './App.css'; 11 | 12 | const exampleList = [ 13 | { 14 | name: 'Joe Smith', 15 | email: 'joesmith@gmail.com', 16 | }, 17 | { 18 | name: 'Alan Donald', 19 | email: 'alan@gmail.com', 20 | }, 21 | ]; 22 | 23 | const getMatchedList = (searchText) => { 24 | if (TypeChecker.isEmpty(searchText)) return exampleList; 25 | return exampleList.filter((item) => item.name.toLowerCase().includes(searchText.toLowerCase())); 26 | }; 27 | 28 | const ExampleList = (props) => ( 29 |
30 |
31 |
    32 |
  • Name
  • 33 |
  • Email
  • 34 |
35 |
36 |
37 | { 38 | props.list.map((item, index) => ( 39 |
    40 |
  • {item.name}
  • 41 |
  • {item.email}
  • 42 |
)) 43 | } 44 |
45 |
46 | ); 47 | 48 | class App extends Component { 49 | constructor(props) { 50 | super(props); 51 | this.state = { 52 | basicExampleList: [...exampleList], 53 | onEnterExampleList: [...exampleList], 54 | onSearchClickExampleList: [...exampleList], 55 | onBlurExampleList: [...exampleList], 56 | }; 57 | this.onBasicExampleChange = this.onBasicExampleChange.bind(this); 58 | this.onEnterExample = this.onEnterExample.bind(this); 59 | this.onSearchClickExample = this.onSearchClickExample.bind(this); 60 | this.onBlurExample = this.onBlurExample.bind(this); 61 | } 62 | 63 | onBasicExampleChange(value) { 64 | this.setState({ 65 | basicExampleList: getMatchedList(value), 66 | }); 67 | } 68 | 69 | onEnterExample(value) { 70 | this.setState({ 71 | onEnterExampleList: getMatchedList(value), 72 | }); 73 | } 74 | 75 | onSearchClickExample(value) { 76 | this.setState({ 77 | onSearchClickExampleList: getMatchedList(value), 78 | }); 79 | } 80 | 81 | onBlurExample(value) { 82 | this.setState({ 83 | onBlurExampleList: getMatchedList(value), 84 | }); 85 | } 86 | 87 | render() { 88 | return ( 89 |
90 |
91 |

React Search Field

92 |
93 | 98 | Star 99 | 100 |
101 |
102 | 107 | Issue 108 | 109 |
110 |
111 | 115 | Sponsor 116 | 117 |
118 |
119 |
120 |
Installation
121 | 122 | {exampleSnippets.installation} 123 | 124 |
125 |
126 |
Basic Example
127 | 128 | {exampleSnippets.basicExample} 129 | 130 | 134 | 137 |
138 |
139 |
Example: onEnter
140 | 141 | {exampleSnippets.onEnterExample} 142 | 143 | 147 | 150 |
151 |
152 |
Example: onSearchClick
153 | 154 | {exampleSnippets.onSearchClickExample} 155 | 156 | 160 | 163 |
164 |
165 |
Example: onBlur
166 | 167 | {exampleSnippets.onBlurExample} 168 | 169 | 173 | 176 |
177 |
178 | ); 179 | } 180 | } 181 | 182 | export default App; 183 | -------------------------------------------------------------------------------- /src/docs/exampleSnippets.js: -------------------------------------------------------------------------------- 1 | export default { 2 | installation: 'npm install react-search-field --save ', 3 | basicExample: `import SearchField from 'react-search-field'; 4 | 5 | `, 9 | onEnterExample: `import SearchField from 'react-search-field'; 10 | 11 | `, 15 | onSearchClickExample: `import SearchField from 'react-search-field'; 16 | 17 | `, 21 | onBlurExample: `import SearchField from 'react-search-field'; 22 | 23 | `, 27 | }; 28 | -------------------------------------------------------------------------------- /src/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Search Field 7 | 8 | 9 | 10 | 11 | Fork me on GitHub 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /src/docs/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | , document.getElementById('root'), 9 | ); 10 | -------------------------------------------------------------------------------- /src/docs/react-search-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nutboltu/react-search-field/51e8856f48edf8146f68b1ccc863ae4d368915b1/src/docs/react-search-field.png -------------------------------------------------------------------------------- /tests/SearchField.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import SearchField from '../src/components/SearchField'; 5 | 6 | describe('', () => { 7 | it('should render with defaults', () => { 8 | expect(shallow()).toHaveLength(1); 9 | }); 10 | it('should render placeholder', () => { 11 | const placeholderText = 'testPlaceholder'; 12 | const component = shallow(); 13 | expect(component.children().at(0).props().placeholder).toEqual(placeholderText); 14 | }); 15 | it('should render searchText', () => { 16 | const searchText = 'testSearchText'; 17 | const component = shallow(); 18 | expect(component.find('input').props().value).toEqual(searchText); 19 | }); 20 | it('should render search button', () => { 21 | const component = shallow(); 22 | expect(component.find('button')).toHaveLength(1); 23 | }); 24 | it('should render SearchIcon', () => { 25 | const component = shallow(); 26 | expect(component.find('SearchIcon')).toHaveLength(1); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/test.setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | 5 | function testConfig() { 6 | configure({ adapter: new Adapter() }); 7 | } 8 | 9 | module.exports = testConfig(); 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const TerserPlugin = require('terser-webpack-plugin'); 5 | 6 | module.exports = { 7 | mode: 'development', 8 | entry: path.resolve(__dirname, 'src/docs/index.jsx'), 9 | output: { 10 | filename: 'bundle.min.js', 11 | path: path.resolve(__dirname, 'docs'), 12 | }, 13 | resolve: { 14 | extensions: ['.js', '.jsx'], 15 | modules: [path.resolve(__dirname, './src'), 'node_modules'], 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.jsx$/, 21 | exclude: /node_modules/, 22 | use: { 23 | loader: 'babel-loader', 24 | }, 25 | }, 26 | { 27 | test: /\.css$/, 28 | use: ['style-loader', 'css-loader'], 29 | }, 30 | ], 31 | }, 32 | devServer: { 33 | contentBase: 'docs', 34 | host: 'localhost', 35 | }, 36 | plugins: [ 37 | new TerserPlugin({ 38 | cache: true, 39 | parallel: true, 40 | terserOptions: { ecma: 8 }, 41 | }), 42 | new HtmlWebpackPlugin({ 43 | template: path.join(__dirname, 'src/docs/index.html'), 44 | }), 45 | new CopyWebpackPlugin({ 46 | patterns: [ 47 | { 48 | from: path.join(__dirname, 'src/docs/react-search-field.png'), 49 | }, 50 | ], 51 | }), 52 | ], 53 | }; 54 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: path.resolve(__dirname, 'src/components/SearchField.jsx'), 7 | output: { 8 | filename: 'index.min.js', 9 | path: path.resolve(__dirname, 'dist'), 10 | library: 'ReactSearch', 11 | libraryTarget: 'umd', 12 | globalObject: 'this', 13 | }, 14 | resolve: { 15 | extensions: ['.js', '.jsx'], 16 | modules: [path.resolve(__dirname, './src'), 'node_modules'], 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx$/, 22 | exclude: /node_modules/, 23 | use: { 24 | loader: 'babel-loader', 25 | }, 26 | }, 27 | ], 28 | }, 29 | plugins: [ 30 | new TerserPlugin({ 31 | cache: true, 32 | parallel: true, 33 | terserOptions: { ecma: 8 }, 34 | }), 35 | ], 36 | externals: { 37 | // Use external version of React 38 | react: { 39 | commonjs: 'react', 40 | commonjs2: 'react', 41 | amd: 'react', 42 | root: 'React', 43 | }, 44 | 'react-dom': { 45 | commonjs: 'react-dom', 46 | commonjs2: 'react-dom', 47 | amd: 'react-dom', 48 | root: 'ReactDOM', 49 | }, 50 | }, 51 | }; 52 | --------------------------------------------------------------------------------