├── .gitignore ├── .travis.yml ├── README.md ├── demo └── src │ ├── index.js │ └── styles.scss ├── nwb.config.js ├── package.json ├── src ├── OptionsTemplate.js └── index.js └── tests ├── .eslintrc └── index-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es6 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 4.2 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | before_install: 12 | - npm install codecov.io coveralls 13 | 14 | after_success: 15 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 16 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 17 | 18 | branches: 19 | only: 20 | - master 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-youtube-autocomplete 2 | A responsive React-based auto-suggest search box for Youtube apps. 3 | 4 | I like to build apps on top of Youtube. Sometimes you need to let users search for videos on Youtube within your app. 5 | Just drop this component into your Youtube-friendly React.js app and you'll get a fully functional auto-suggest-enabled search box. 6 | 7 | ## Demo 8 | 9 | See this [compenent in action](http://hackingbeauty.github.io/react-youtube-autocomplete/) 10 | 11 | ## Installation 12 | 13 | `npm install --save react-youtube-autocomplete` 14 | 15 | ## Features 16 | 17 | - Autocomplete text entry 18 | - Search Youtube based on text input 19 | - Retrieve list of results from Youtube 20 | - Display drop-down list of search results 21 | 22 | ## Usage 23 | 24 | ```js 25 | 50. Number of video search results you want 28 | placeHolder={string} // defaults -> "Search Youtube" 29 | callback={function} // callback to execute when search results are retrieved 30 | className={string} // defaults -> random string 31 | /> 32 | ``` 33 | 34 | ## Example 35 | 36 | ```js 37 | import YoutubeAutocomplete from 'react-youtube-autocomplete'; 38 | 39 | class Example extends React.Component { 40 | render() { 41 | return ( 42 | 47 | ); 48 | } 49 | 50 | _onSearchResultsFound(results) { 51 | // Results is an array of retreived search results 52 | // I use flux, so I dispatch results to an action 53 | flux.actions.showSearchResults(results); 54 | } 55 | } 56 | ``` 57 | 58 | ## License 59 | 60 | MIT 61 | 62 | ## Course 63 | 64 | Are you looking to build a professional app for the Web using React & Redux? 65 | 66 | Check out my course ["How to Write a Single Page Application".](http://www.singlepageapplication.com) 67 | 68 | www.singlepageapplication.com -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import {render} from 'react-dom'; 3 | import { Dialog } from 'material-ui'; 4 | import injectTapEventPlugin from 'react-tap-event-plugin'; 5 | import Code from 'react-embed-code'; 6 | 7 | import Component from '../../src'; 8 | 9 | injectTapEventPlugin(); 10 | 11 | require('./styles.scss'); 12 | 13 | const cssDownload = ` 14 | .react-typeahead-options { 15 | margin: 0; 16 | padding: 0; 17 | list-style-type: none; 18 | border: 1px solid #ccc; 19 | cursor: default; 20 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 21 | z-index: 1; 22 | width: 100%; 23 | } 24 | 25 | .react-typeahead-options { 26 | margin-left: -8px; 27 | } 28 | 29 | .react-typeahead-options li[role="option"] { 30 | padding: 5px; 31 | text-align: left; 32 | cursor: pointer; 33 | } 34 | 35 | .react-typeahead-options li[role="option"][aria-selected="true"] { 36 | background: #00bcd4; 37 | color: white; 38 | font-weight: bold; 39 | } 40 | 41 | .react-typeahead-container { 42 | border: 1px solid #024e6a; 43 | padding: 5px 8px; 44 | border-radius: 0px; 45 | background-color: white; 46 | margin: 0 auto 47 | } 48 | 49 | .react-typeahead-input { 50 | position: relative; 51 | background: white; 52 | outline: none; 53 | width: 100%; 54 | font-size: 24px; 55 | line-height: 30px; 56 | border: none; 57 | } 58 | `; 59 | 60 | const componentEmbed = ` 61 | import YoutubeAutocomplete from 'react-youtube-autocomplete'; 62 | 63 | 68 | `; 69 | 70 | const Demo = React.createClass({ 71 | getInitialState() { 72 | return { 73 | open: false, 74 | searchResults : [] 75 | } 76 | }, 77 | 78 | getFormattedResults(searchResults) { 79 | return searchResults.map(function(result) { 80 | return
  • 81 | 82 | {result.snippet.title} 83 | {result.snippet.title} 84 | 85 |
  • 86 | }); 87 | }, 88 | 89 | showResults(searchResults) { 90 | this.setState({ 91 | open : true, 92 | searchResults : searchResults 93 | }) 94 | }, 95 | 96 | handleClose() { 97 | this.setState({open: false}); 98 | }, 99 | 100 | onRequestClose() { 101 | this.setState({open: false}); 102 | }, 103 | 104 | render() { 105 | var searchResults = this.state.searchResults; 106 | var formattedResults; 107 | 108 | if(searchResults) { 109 | formattedResults = this.getFormattedResults(searchResults); 110 | } 111 | 112 | return
    113 |
    114 |
    115 |
    116 |
    117 |
    118 |
    119 |

    react-youtube-autocomplete

    120 |

    A responsive & React-based auto-suggest search box for Youtube apps

    121 |
    122 | 125 |
    126 |
    127 |
    128 |
    129 | Demo 130 |
    131 |
    132 |
    133 | 139 |
    140 |
    141 |
    142 |
    143 |
    144 | Step 1 - NPM install 145 |
    146 |
    147 |
    npm install --save react-youtube-autocomplete
    148 |
    149 |
    150 | Step 2 - Embed component 151 |
    152 |
    153 |
    154 | 155 |
    156 |
    157 |
    158 | Step 3 - Download base styles 159 |
    160 |
    161 |
    162 | 163 |
    164 |
    165 |
    166 |
    167 | 170 | 175 |
      176 | {formattedResults} 177 |
    178 |
    179 | 180 |
    181 | } 182 | }) 183 | 184 | render(, document.querySelector('#demo')) 185 | -------------------------------------------------------------------------------- /demo/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* component styles */ 2 | /* ---------------- */ 3 | 4 | .react-typeahead-options { 5 | margin: 0; 6 | padding: 0; 7 | list-style-type: none; 8 | border: 1px solid #ccc; 9 | cursor: default; 10 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 11 | z-index: 1; 12 | width: 100%; 13 | } 14 | 15 | .react-typeahead-options { 16 | margin-left: -8px; 17 | } 18 | 19 | .react-typeahead-options li[role="option"] { 20 | padding: 5px; 21 | text-align: left; 22 | cursor: pointer; 23 | } 24 | 25 | .react-typeahead-options li[role="option"][aria-selected="true"] { 26 | background: #00bcd4; 27 | color: white; 28 | font-weight: bold; 29 | } 30 | 31 | .react-typeahead-container { 32 | border: 1px solid #024e6a; 33 | padding: 5px 8px; 34 | border-radius: 0px; 35 | background-color: white; 36 | margin: 0 auto 37 | } 38 | 39 | .react-typeahead-input { 40 | position: relative; 41 | background: white; 42 | outline: none; 43 | width: 100%; 44 | font-size: 24px; 45 | line-height: 30px; 46 | border: none; 47 | } 48 | 49 | /* base styles */ 50 | /* ----------- */ 51 | 52 | 53 | html, 54 | body { 55 | background-color: white; 56 | font: normal normal normal 18px/1.2 "Helvetica Neue", Roboto, "Segoe UI", Calibri, sans-serif; 57 | color: #292f33; 58 | padding-top: 20px; 59 | margin: 0; 60 | } 61 | 62 | .container { 63 | max-width: 900px; 64 | margin: 0 auto; 65 | text-align: center; 66 | } 67 | 68 | #main { 69 | max-width: 700px; 70 | margin:0 auto; 71 | padding:0 20px 20px 20px; 72 | } 73 | 74 | #demo-box { 75 | max-width: 490px; 76 | margin:0 auto; 77 | } 78 | 79 | h1 { 80 | color: #00bcd4; 81 | margin: 0; 82 | } 83 | 84 | a { 85 | color: #00bcd4; 86 | } 87 | 88 | ul, 89 | li { 90 | margin:0px; 91 | } 92 | 93 | #github-links { 94 | position: relative; 95 | top: 9px; 96 | display:inline-block; 97 | } 98 | 99 | iframe { 100 | margin-top: 10px; 101 | margin-left: 20px; 102 | } 103 | 104 | footer { 105 | padding:30px; 106 | border-top: 1px solid #dddddd; 107 | background: #efefef; 108 | } 109 | 110 | .text-left{ 111 | text-align: left; 112 | } 113 | 114 | .smaller-text { 115 | font-size: 16px; 116 | } 117 | 118 | .headline { 119 | margin-top: 30px; 120 | } 121 | 122 | .youtube-icon, 123 | .react-icon { 124 | display: inline-block; 125 | margin:0 20px; 126 | } 127 | 128 | .youtube-icon { 129 | height: 68px; 130 | width: 105px; 131 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIgAAABfCAYAAAAzgY/FAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGODdGMTE3NDA3MjA2ODExOERCQkVDMzREQUI5OTZGRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBOTA3NTU1REYyRDYxMUU1ODA2MkM0RDdFNzI3RDQwNSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBOTA3NTU1Q0YyRDYxMUU1ODA2MkM0RDdFNzI3RDQwNSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Rjg3RjExNzQwNzIwNjgxMThEQkJFQzM0REFCOTk2RkQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6Rjg3RjExNzQwNzIwNjgxMThEQkJFQzM0REFCOTk2RkQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7gtwGqAAAExElEQVR42uydW4hVVRjH11wsFTG7iSUxqOMEaZYZQaYGNZqW1kNFUA8ZFFo92EMvRfQilASBERl4KQstL0QJhpeSrKaI8lIQQ0gwZTfClMwkuo39P9c6cmY36zjn1nTW/v3gh3jhDPtbf7+99tr7rN3kqqNFzpDTZYccJYfIEw4Ggyb5p/xZHpAfyi75dzUfWAlXy5vlXDkphAL+f/whu+V2uUV+VO8fOFu+GzoENp67wxjWnA6CkVxQJtYqHItlL0VNTpuXLKo2HMspZPIurzQcL1K83PhCueF4hqLRSWLcT7Fy6+LTrYPYzHafHMESQi75VU6VXxb+oLmfUwvhyC8jQgb6ZR4tFoNz++sgS/gPBIGHsnMQu+H2PnWBImbKrkIHuYl6QIYbC6cY6yKd1AMyWCaaLCDXysnUAzJYJmZZQOzZjqHUAzIMs2xYQDqoBUTosICMSvTgDjq/MgiVc7YFpDXRg1sqbyckVdFauIpJkQucfxbzYrmLsa6Ik1cxqT6B3hJ+/T5cst0rDzPmZXGiOUcHu0ZeKncw7gOnOWfH+4PzN6LukUcYfgISY63zz768RgQISAzrILfJu+SPRIGAxHjF+cXCTZSCgMT4Rd4ROsq3lIOAxLA5iX3X+FVKQUBKdZM75QK6CQEpxdYwN1lNQCDGb/I+OV9+Q0AgxptyglxBQCCG7dzzoLzOFX2xiIBAlnfkFPk8AYFSc5MHnH+e9wABgRjvOf9w79MEBErNTR52/otGXxAQiGHbTV4inyQgEMOezntUXin3ExCIsTeEZKlr8Ec6CUj9sJ0hH5fT5CcEBGLYqeYq+Yj8i4BAjGWyXa4jIBDja+d3rG4YWhmz/4zLnN+P9Ao6CGRrbPOPTxstHHSQ+nO5fMn5m3tcxcApznB+DWR/I4eDDlIfpoWuMSmV8yPUhjPlE3JPKuGgg9SOa5z/Omd7ijNsqJzh8inn7+S2p3iAdJDqusbLcnzq1+hQHiPls6FrjE/9YOkg5TErdI22vBwwHWRg2GsynnP+Pkpbng6cDnJ6bM9y275qTB4Png4S5xy50vlv1Y3JaxHoIP1jb7+wO6+j814IOkhfRodJ6FbCQQfJcotcJc+nFHSQbNfYIN8gHHSQLLaTkH0JeyRRoIMUc6HcLNcTDjpIlrud3whmOMNPBymmLVydrCUcBMQofs3JQvm54+2enGKK+Mr5d/FtkXMYajpIllvlPsJBB4nB6YQOAgQEBj0gTZQBYleCFpBe6gARei0gx6kDRDhuATlEHSDCIQvIXuoAEfZYQGzvcV7DBVksE7stIAddA+/CB3XjY8tGYR1kG/WADCczUVgDsUf8bYuki6gLhLPKVHmk0EHsJcMbqAsENoZM9FlFHSs/k+dSn1xz2PkdGb+z37QU/cUx519tcQM1yjWPyZ2l/sHbzm9Aj/nzrYEkaJzsoVi5syeMfR9id3LtVRYfOL+dI6TP787vmPSvVfXY8yC2U9/1YV4CaWNj3OkqvOVi2zl2036TtdvVYMvOYc5/qZmCpqXtfTK0lq2oM8xLKG5j2xXGsm7Yh78ezl0UvDE8Fsas7GBU8zzqeeEHTg+XR7YSe5YcwrxvULHFzqNhJbQndP1d8qdKPuwfAQYAn/4rU9XrS/QAAAAASUVORK5CYII=); 132 | background-repeat: no-repeat; 133 | background-size:contain; 134 | } 135 | 136 | .react-icon { 137 | height: 68px; 138 | width: 74px; 139 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF4AAABUCAIAAACEKxdfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBOTA3NTU2MEYyRDYxMUU1ODA2MkM0RDdFNzI3RDQwNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBOTA3NTU2MUYyRDYxMUU1ODA2MkM0RDdFNzI3RDQwNSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE5MDc1NTVFRjJENjExRTU4MDYyQzREN0U3MjdENDA1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE5MDc1NTVGRjJENjExRTU4MDYyQzREN0U3MjdENDA1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+a5NzNAAAFuxJREFUeNrEnHlYlFX7x5FsXy1M28z2EjUFlcq1xdwyt65Ag4TIJTXT5LJS00qN0qjMvYVSsy5BCRVBCCxcMBKVlFBQE0UykzSyxbL092m+73t+884Mw8wzOJ4/5nrmmfOc5T73/b2/933OM3VOnToVUEslLy+vuLj4yJEjZ511Vv369Rs3bhwSEnLRRRcFnJ5SXl5eUFCwd+/eQ4cO/fPPP/TYsmXLBx98sNY6OFUb5cMPP2zduvV5551n3/K55557ww03REZGLly48M8//zxVS+XHH3+cMWNGjx49kIXDXOrWrdu8efPExMRa6aiO71oTHh6elJR0zjnnMNbzzz8/MDCQmyzjX3/99csvv1RVVfH1tttui4iIGDly5OWXX265IxTkjTfeSE5OPnz4cJ06dYKCgi688MKzzz6ba1U4fvw4GsQyxMbGvv/++2dYawYMGEAj2A6Tb9iwoX3LjPu6667j/o033sg1d6655pr4+HgLvTDnF154QbZZr169W265hWavuuoq5GLfY4MGDW6//XY65Xr06NFnUmsWLFgQHR19/fXXY0p79uzh86GHHrrjjjtOnDhRVlZWWFhYUlLCNZOR8v/www/oUYsWLSZPnkxND3tJTU19/vnnaQqNu/LKK9HHgwcP/vrrr0gqNDS0VatWjRo1ohrdoVCoKitx7NixioqKjIyMrl27ngGt+f3336+++mrEgSwwchRn8+bN9hWYw5o1a4YPH85i0tFll13GkrLgVObrk08+WSMAIVaqCbZuvfVWNOXiiy/mK5MfP348snCozwBQJYbUpEkTrIw18EVrrItGxnzzzTdj8wwdNamuJvY/YcIEocy1117LDLEsrpHpli1bqntq+/btwcHBegSZsgwyyWnTpv3222/VPYV0qIaA8ABcrFq16gyIBh9B3wyaz+eee67G+vv378dbURnARgVQH13j3ZwrL1my5IILLqCClEWYMmjQINxTjR3FxMSYgT3++OP+Fg3khZUBRFhMvNLOnTs9fDAtLQ1FY9AgFFpz6aWXcg2U2NeZMmUKN7EdKlBNAsrMzPSwiy+//BJrwsAZGAL6+++//Sqa1atXa3qs51133eXVsyCx/NoVV1zB5BGx/fLi4PmKs+MngTeVAV3P2wfjUDR8ItaHpW/atMmvopk7dy6DllGAlBZagLbxrFBcqjFixAgMU+DCTblqiIyFxvv168ez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); 140 | background-repeat: no-repeat; 141 | background-size: contain; 142 | } 143 | 144 | .code-snippet { 145 | border: 1px solid #dddddd; 146 | background: #efefef; 147 | padding: 10px; 148 | } 149 | 150 | #search-result-list { 151 | display: inline-block; 152 | padding-left:0px; 153 | } 154 | 155 | .search-result { 156 | cursor: pointer; 157 | float: left; 158 | width: 30%; 159 | height: 120px; 160 | padding:0 10px; 161 | list-style-position: inside; 162 | list-style-type:none; 163 | } 164 | 165 | .search-result img { 166 | max-width: 100px; 167 | float: left; 168 | margin-right: 10px; 169 | } 170 | 171 | .search-result span { 172 | font-size: 15px; 173 | width: 87px; 174 | height: 74px; 175 | display: inline-block; 176 | color: black; 177 | overflow: hidden; 178 | } 179 | 180 | 181 | @media only screen and (max-width: 1012px) { 182 | .search-result { 183 | width: 29%; 184 | height: 168px 185 | } 186 | } 187 | 188 | @media only screen and (max-width: 815px) { 189 | .search-result { 190 | width: 100%; 191 | height: 100px 192 | } 193 | } 194 | 195 | @media only screen and (max-width: 500px) { 196 | #github-links { 197 | display: block; 198 | } 199 | 200 | } -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Let nwb know this is a React component module when generic build commands 3 | // are used. 4 | type: 'react-component', 5 | 6 | // Should nwb create a UMD build for this module? 7 | umd: true, 8 | // The name of the global variable the UMD build of this module will export 9 | global: 'YoutubeAutocomplete', 10 | // Mapping from the npm package names of this module's peerDependencies to the 11 | // global variables they're expected to be available as for use by the UMD 12 | // build. 13 | externals: { 14 | 'react': 'React' 15 | }, 16 | 17 | // Should nwb create a build with untranspiled ES6 modules for tree-shaking 18 | // module bundlers? If you change your mind later, add or remove this line in 19 | // package.json: "jsnext:main": "es6/index.js" 20 | jsNext: true 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-youtube-autocomplete", 3 | "version": "1.0.19", 4 | "description": "A responsive & React-based auto-suggest search box for Youtube apps", 5 | "keywords": [ 6 | "react-component" 7 | ], 8 | "main": "lib/index.js", 9 | "jsnext:main": "es6/index.js", 10 | "files": [ 11 | "css", 12 | "es6", 13 | "lib", 14 | "umd" 15 | ], 16 | "scripts": { 17 | "build": "nwb build", 18 | "clean": "nwb clean", 19 | "start": "nwb serve", 20 | "test": "nwb test", 21 | "deploy": "gh-pages -d demo/dist" 22 | }, 23 | "dependencies": { 24 | "jsonp": "^0.2.0", 25 | "react-typeahead-component2": "^0.10.2", 26 | "youtube-finder": "^1.0.0" 27 | }, 28 | "peerDependencies": { 29 | "react": "^15.0.0", 30 | "react-dom": "^15.0.0" 31 | }, 32 | "devDependencies": { 33 | "gh-pages": "^0.12.0", 34 | "material-ui": "0.14.4", 35 | "nwb": "0.7.x", 36 | "nwb-sass": "^0.5.0", 37 | "react": "0.14.x", 38 | "react-dom": "0.14.x", 39 | "react-embed-code": "^0.1.0", 40 | "react-tap-event-plugin": "^0.2.2" 41 | }, 42 | "author": "", 43 | "homepage": "", 44 | "license": "MIT", 45 | "repository": "https://github.com/hackingbeauty/react-youtube-autocomplete" 46 | } 47 | -------------------------------------------------------------------------------- /src/OptionsTemplate.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default React.createClass({ 4 | render: function() { 5 | var searchResult = this.props.data[0]; 6 | return ( 7 |
    {searchResult}
    8 | ); 9 | } 10 | }); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Typeahead from 'react-typeahead-component2'; 3 | import JSONP from 'jsonp'; 4 | import OptionsTemplate from './OptionsTemplate'; 5 | import YoutubeFinder from 'youtube-finder'; 6 | 7 | const googleAutoSuggestURL = '//suggestqueries.google.com/complete/search?client=youtube&ds=yt&q='; 8 | 9 | class YoutubeAutocomplete extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | inputValue: '' 15 | } 16 | } 17 | 18 | handleChange(event) { 19 | const 20 | self = this, 21 | query = event.target.value, 22 | url = googleAutoSuggestURL + query; 23 | 24 | this.setState({ 25 | inputValue: query 26 | }); 27 | 28 | JSONP(url, function(error, data){ 29 | if (error) { 30 | console.log(error); 31 | } else { 32 | const searchResults = data[1]; 33 | self.setState({ 34 | options: searchResults 35 | }); 36 | } 37 | }); 38 | } 39 | 40 | onClick(event, optionData) { 41 | const searchTerm = optionData[0]; 42 | this.setState({ 43 | inputValue: searchTerm 44 | }); 45 | } 46 | 47 | onOptionChange(event, optionData, index) { 48 | const 49 | self = this, 50 | searchTerm = optionData[0], 51 | apiKey = this.props.apiKey, 52 | maxResults = this.props.maxResults ? this.props.maxResults : '50'; 53 | 54 | this.setState({ 55 | inputValue: searchTerm 56 | }); 57 | } 58 | 59 | onDropDownClose(event) { 60 | const 61 | self = this, 62 | searchTerm = this.state.inputValue, 63 | maxResults = this.props.maxResults <= 50 ? this.props.maxResults : '50', 64 | YoutubeClient = YoutubeFinder.createClient({ key: this.props.apiKey }), 65 | params = { 66 | part : 'id,snippet', 67 | type : 'video', 68 | q : searchTerm, 69 | maxResults : maxResults 70 | }; 71 | 72 | YoutubeClient.search(params, function(error,results){ 73 | if(error) return console.log(error); 74 | self.props.callback(results.items); 75 | }); 76 | 77 | } 78 | 79 | render() { 80 | // React components using ES6 classes no longer autobind this to non React methods. In your constructor, add: 81 | // this.onChange = this.onChange.bind(this) 82 | // this is why you have to do onChange={this.handleChange.bind(this)} 83 | return
    84 | 95 |
    96 | } 97 | } 98 | 99 | export default YoutubeAutocomplete; 100 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | --------------------------------------------------------------------------------