├── .gitignore ├── LICENSE.txt ├── README.md ├── demo ├── demo.mp4 ├── index.html ├── scripts.js └── styles.css ├── octopalm.js └── octopalm.ladybug.js /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/.gitignore -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Eddie Gulay 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/README.md -------------------------------------------------------------------------------- /demo/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/demo/demo.mp4 -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Real-Time Search 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/scripts.js: -------------------------------------------------------------------------------- 1 | // scripts.js 2 | 3 | // Sample data 4 | const items = [ 5 | // Food 6 | { label: "Apple", link: "/food/apple", emoji: "🍎" }, 7 | { label: "Banana", link: "/food/banana", emoji: "🍌" }, 8 | { label: "Orange Juice", link: "/food/orange-juice", emoji: "🍊" }, 9 | { label: "Burger", link: "/food/burger", emoji: "🍔" }, 10 | { label: "Pizza", link: "/food/pizza", emoji: "🍕" }, 11 | { label: "Coffee", link: "/food/coffee", emoji: "☕" }, 12 | { label: "Hotdog", link: "/food/hotdog", emoji: "🌭" }, 13 | { label: "Ice Cream", link: "/food/ice-cream", emoji: "🍦" }, 14 | { label: "Pasta", link: "/food/pasta", emoji: "🍝" }, 15 | { label: "Sushi", link: "/food/sushi", emoji: "🍣" }, 16 | 17 | // Clothes 18 | { label: "T-Shirt", link: "/clothes/t-shirt", emoji: "👕" }, 19 | { label: "Jeans", link: "/clothes/jeans", emoji: "👖" }, 20 | { label: "Jacket", link: "/clothes/jacket", emoji: "🧥" }, 21 | { label: "Sneakers", link: "/clothes/sneakers", emoji: "👟" }, 22 | { label: "Dress", link: "/clothes/dress", emoji: "👗" }, 23 | { label: "Sweater", link: "/clothes/sweater", emoji: "🧣" }, 24 | 25 | // Vehicles 26 | { label: "Car", link: "/vehicles/car", emoji: "🚗" }, 27 | { label: "Motorcycle", link: "/vehicles/motorcycle", emoji: "🏍️" }, 28 | { label: "Bicycle", link: "/vehicles/bicycle", emoji: "🚲" }, 29 | { label: "Truck", link: "/vehicles/truck", emoji: "🚚" }, 30 | 31 | // Health 32 | { label: "Vitamins", link: "/health/vitamins", emoji: "💊" }, 33 | { label: "First Aid Kit", link: "/health/first-aid-kit", emoji: "🩹" }, 34 | { label: "Yoga Mat", link: "/health/yoga-mat", emoji: "🧘‍♀️" }, 35 | { label: "Dumbbells", link: "/health/dumbbells", emoji: "🏋️‍♂️" }, 36 | { label: "Thermometer", link: "/health/thermometer", emoji: "🌡️" } 37 | ]; 38 | 39 | 40 | new OctoPalm('search-input', items); -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | body { 3 | font-family: Arial, sans-serif; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | .search-container { 9 | position: relative; 10 | width: 300px; 11 | margin: 50px auto; 12 | } 13 | 14 | #search-input { 15 | width: 100%; 16 | padding: 8px; 17 | box-sizing: border-box; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /octopalm.js: -------------------------------------------------------------------------------- 1 | class OctoPalm { 2 | constructor(inputId, items) { 3 | this.inputElement = document.getElementById(inputId); 4 | this.items = items; 5 | this.resultsContainer = this.createResultsContainer(); 6 | this.injectStyles(); 7 | 8 | this.initialize(); 9 | } 10 | 11 | initialize() { 12 | if (!this.inputElement) { 13 | console.error(`Input element with id ${this.inputElement.id} not found.`); 14 | return; 15 | } 16 | 17 | this.inputElement.addEventListener('input', () => this.performSearch()); 18 | } 19 | 20 | createResultsContainer() { 21 | const container = document.createElement('div'); 22 | container.className = 'opalm-search-results'; 23 | this.inputElement.parentElement.appendChild(container); 24 | return container; 25 | } 26 | 27 | injectStyles() { 28 | const style = document.createElement('style'); 29 | style.type = 'text/css'; 30 | style.textContent = ` 31 | .opalm-search-results { 32 | position: absolute; 33 | top: 100%; 34 | left: 0; 35 | width: 100%; 36 | border: 1px solid #ccc; 37 | background: #fff; 38 | max-height: 320px; 39 | overflow-y: auto; 40 | z-index: 1000; 41 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 42 | border-radius: 5px; 43 | transition: opacity 0.3s ease, transform 0.3s ease; 44 | opacity: 0; 45 | transform: scaleY(0); 46 | transform-origin: top; 47 | scrollbar-width: thin; 48 | scrollbar-color: #000 #fff; 49 | } 50 | .opalm-search-results.show { 51 | opacity: 1; 52 | transform: scaleY(1); 53 | } 54 | .opalm-search-result-item { 55 | padding: 12px 16px; 56 | border-bottom: 1px solid #eee; 57 | display: flex; 58 | align-items: center; 59 | transition: background-color 0.3s ease, transform 0.3s ease; 60 | cursor: pointer; 61 | } 62 | .opalm-search-result-item:hover { 63 | background-color: #f0f0f0; 64 | transform: translateX(5px); 65 | } 66 | .opalm-search-result-item a { 67 | text-decoration: none; 68 | color: #333; 69 | font-weight: 600; 70 | } 71 | .opalm-search-result-item a:hover { 72 | text-decoration: underline; 73 | } 74 | 75 | .opalm-search-results::-webkit-scrollbar { 76 | width: 8px; 77 | } 78 | .opalm-search-results::-webkit-scrollbar-track { 79 | background: #fff; 80 | } 81 | .opalm-search-results::-webkit-scrollbar-thumb { 82 | background: #333; 83 | border-radius: 10px; 84 | } 85 | .opalm-search-results::-webkit-scrollbar-thumb:hover { 86 | background: #000; 87 | } 88 | `; 89 | document.head.appendChild(style); 90 | } 91 | 92 | performSearch() { 93 | const query = this.inputElement.value.toLowerCase(); 94 | this.resultsContainer.innerHTML = ''; 95 | 96 | if (query.length === 0) { 97 | this.resultsContainer.classList.remove('show'); 98 | return; 99 | } 100 | 101 | const filteredItems = this.items.filter(item => 102 | item.itemName.toLowerCase().includes(query) 103 | ); 104 | 105 | filteredItems.forEach(item => { 106 | const resultItem = document.createElement('div'); 107 | resultItem.className = 'opalm-search-result-item'; 108 | resultItem.innerHTML = `${item.itemName}`; 109 | this.resultsContainer.appendChild(resultItem); 110 | }); 111 | 112 | this.resultsContainer.classList.add('show'); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /octopalm.ladybug.js: -------------------------------------------------------------------------------- 1 | class OctoPalm { 2 | constructor(inputId, items) { 3 | this.inputElement = document.getElementById(inputId); 4 | this.items = items; 5 | this.itemsByKey = {}; 6 | this.resultsContainer = this.createResultsContainer(); 7 | this.injectStyles(); 8 | this.initialize(); 9 | } 10 | 11 | initialize() { 12 | if (!this.inputElement) { 13 | console.error(`Input element with id ${this.inputElement.id} not found.`); 14 | return; 15 | } 16 | this.organizeItemsByKey(); 17 | this.inputElement.addEventListener('input', () => this.performSearch()); 18 | } 19 | 20 | organizeItemsByKey() { 21 | this.items 22 | .filter(item => item && typeof item === 'object') 23 | .forEach(item => { 24 | Object.keys(item).forEach(key => { 25 | if (!this.itemsByKey[key]) { 26 | this.itemsByKey[key] = []; 27 | } 28 | this.itemsByKey[key].push(item); 29 | }); 30 | }); 31 | } 32 | 33 | createResultsContainer() { 34 | const container = document.createElement('div'); 35 | container.className = 'opalm-search-results'; 36 | this.inputElement.parentElement.appendChild(container); 37 | return container; 38 | } 39 | 40 | injectStyles() { 41 | const style = document.createElement('style'); 42 | style.type = 'text/css'; 43 | style.textContent = ` 44 | .opalm-search-results { 45 | position: absolute; 46 | top: 100%; 47 | left: 0; 48 | width: 100%; 49 | border: 1px solid #ccc; 50 | background: #fff; 51 | max-height: 320px; 52 | overflow-y: auto; 53 | z-index: 1000; 54 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 55 | border-radius: 5px; 56 | transition: opacity 0.3s ease, transform 0.3s ease; 57 | opacity: 0; 58 | transform: scaleY(0); 59 | transform-origin: top; 60 | scrollbar-width: thin; 61 | scrollbar-color: #000 #fff; 62 | } 63 | .opalm-search-results.show { 64 | opacity: 1; 65 | transform: scaleY(1); 66 | } 67 | .opalm-search-result-item { 68 | padding: 12px 16px; 69 | border-bottom: 1px solid #eee; 70 | display: flex; 71 | align-items: center; 72 | transition: background-color 0.3s ease, transform 0.3s ease; 73 | cursor: pointer; 74 | } 75 | .opalm-search-result-item:hover { 76 | background-color: #f0f0f0; 77 | transform: translateX(5px); 78 | } 79 | .opalm-search-result-item a { 80 | text-decoration: none; 81 | color: #333; 82 | font-weight: 600; 83 | } 84 | .opalm-search-result-item a:hover { 85 | text-decoration: underline; 86 | } 87 | 88 | .opalm-search-results::-webkit-scrollbar { 89 | width: 8px; 90 | } 91 | .opalm-search-results::-webkit-scrollbar-track { 92 | background: #fff; 93 | } 94 | .opalm-search-results::-webkit-scrollbar-thumb { 95 | background: #333; 96 | border-radius: 10px; 97 | } 98 | .opalm-search-results::-webkit-scrollbar-thumb:hover { 99 | background: #000; 100 | } 101 | `; 102 | document.head.appendChild(style); 103 | } 104 | 105 | performSearch() { 106 | const query = this.inputElement.value.toLowerCase(); 107 | this.resultsContainer.innerHTML = ''; 108 | 109 | if (query.length === 0) { 110 | this.resultsContainer.classList.remove('show'); 111 | return; 112 | } 113 | 114 | const filteredResults = this.getFilteredResults(query); 115 | const uniqueResults = this.removeDuplicates(filteredResults); 116 | this.displayResults(uniqueResults); 117 | } 118 | 119 | getFilteredResults(query) { 120 | const filteredResults = []; 121 | for (let key in this.itemsByKey) { 122 | const filteredItems = this.itemsByKey[key].filter(item => { 123 | const value = item[key]; 124 | return value && typeof value === 'string' && value.toLowerCase().includes(query); 125 | }); 126 | filteredResults.push(...filteredItems); 127 | } 128 | return filteredResults; 129 | } 130 | 131 | removeDuplicates(items) { 132 | const uniqueItems = []; 133 | const seen = new Set(); 134 | 135 | items.forEach(item => { 136 | const identifier = item.itemName || item.name || item.label || item.item || 'Unknown Item'; 137 | if (!seen.has(identifier)) { 138 | seen.add(identifier); 139 | uniqueItems.push(item); 140 | } 141 | }); 142 | 143 | return uniqueItems; 144 | } 145 | 146 | displayResults(filteredResults) { 147 | filteredResults.forEach(item => { 148 | const resultItem = document.createElement('div'); 149 | resultItem.className = 'opalm-search-result-item'; 150 | let itemName = item.itemName || item.name || item.label || item.item || 'Unknown Item'; 151 | 152 | try { 153 | if (item.emoji) { 154 | itemName = item.emoji + " " + itemName; 155 | } 156 | } catch (error) {} 157 | 158 | resultItem.innerHTML = `${itemName}`; 159 | this.resultsContainer.appendChild(resultItem); 160 | }); 161 | 162 | if (filteredResults.length > 0) { 163 | this.resultsContainer.classList.add('show'); 164 | } else { 165 | this.resultsContainer.classList.remove('show'); 166 | } 167 | } 168 | } 169 | --------------------------------------------------------------------------------