GL Search
Version: 1.1.0 Package:
@gift-card-market/gl-search
Last Updated: October 5, 2026
A reusable React search component with location input and Google Maps integration for Gift Card Market.
Features
- 🔍 Search with Location - Integrated Google Maps location autocomplete
- 🎨 Customizable UI - Configure banner colors, texts, and search button styling
- ⚡ Debounced Search - Configurable debounce time for better performance
- 🎯 Custom Events - Dispatch search and clear events for easy integration
- 📱 Responsive Design - Works seamlessly on all devices
- 🌐 Vanilla JS Support - Use as Web Component without React
- 💪 TypeScript - Full type definitions included
Table of Contents
- Installation
- Integration
- API Reference
- Custom Events & Shadow DOM
- Event Handling
- Complete Examples
- TypeScript Support
- Browser Support
- License
- Support
- Contributing
- Changelog
Installation
Using npm
npm install @gift-card-market/gl-search
Using yarn
yarn add @gift-card-market/gl-search
Integration
React Integration
1. Import the Component and Styles
import { GlSearch } from '@gift-card-market/gl-search';
import '@gift-card-market/gl-search/dist/style.css';
2. Basic Usage
import React from 'react';
import { GlSearch } from '@gift-card-market/gl-search';
import '@gift-card-market/gl-search/dist/style.css';
function App() {
return (
<div>
<GlSearch
environment="development"
jwt="your-jwt-token-here"
/>
</div>
);
}
export default App;
3. Advanced Usage with All Options
import React, { useRef } from 'react';
import { GlSearch } from '@gift-card-market/gl-search';
import type { GlSearchBarHandle } from '@gift-card-market/gl-search';
import '@gift-card-market/gl-search/dist/style.css';
function App() {
const searchRef = useRef<GlSearchBarHandle>(null);
// Configuration data
const searchData = {
banner_color: 'darkblue',
banner_text: [
`Search for your <strong>favorite</strong> restaurants! 🍔`,
`Find the perfect gift card 🎁`,
],
search_text: [
{
content: `(thinking <u>top burger joints</u>?)`,
key: 'burgers',
},
{
content: `(thinking <u>best pizza places</u>?)`,
key: 'pizza',
},
],
search_term: '',
search_button_color: {
text: 'yellow',
background: 'green',
},
};
return (
<div>
<GlSearch
ref={searchRef}
environment="development"
jwt="your-jwt-token-here"
google_api_key="your-google-api-key"
data={searchData}
debounce_ms={250}
query="Location=New York&SearchTerm=pizza"
/>
</div>
);
}
export default App;
Vanilla JS Integration
1. Include Scripts and Styles
After publishing, include the bundled JavaScript and CSS files from the dist/browser folder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GlSearch - Vanilla JS Demo</title>
<!-- Include the bundled JS and CSS from published package -->
<script src="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.global.js"></script>
<link rel="stylesheet" href="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.css">
</head>
<body>
<div id="search-container"></div>
<script>
// Your initialization code here
</script>
</body>
</html>
2. Basic Usage
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GlSearch - Vanilla JS Demo</title>
<script src="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.global.js"></script>
<link rel="stylesheet" href="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.css">
</head>
<body>
<div id="search-container"></div>
<script>
// Initialize the web component
function initializeComponent() {
const container = document.getElementById('search-container');
// Create the custom element
const searchElement = document.createElement('gl-search');
// Set required attributes
searchElement.setAttribute('environment', 'development');
searchElement.setAttribute('jwt', 'your-jwt-token-here');
// Append to container
container.appendChild(searchElement);
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponent);
} else {
initializeComponent();
}
</script>
</body>
</html>
3. Advanced Usage with All Options
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GlSearch - Vanilla JS Advanced Demo</title>
<script src="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.global.js"></script>
<link rel="stylesheet" href="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.css">
</head>
<body>
<div id="search-container"></div>
<script>
// Configuration data
const searchData = {
banner_color: 'darkblue',
banner_text: [
`Search for your <strong>favorite</strong> restaurants! 🍔`,
`Find the perfect gift card 🎁`,
],
search_text: [
{
content: `(thinking <u>top burger joints</u>?)`,
key: 'burgers',
},
{
content: `(thinking <u>best pizza places</u>?)`,
key: 'pizza',
},
],
search_term: '',
search_button_color: {
text: 'yellow',
background: 'green',
},
};
// Store reference to the web component
let searchElement = null;
// Initialize the web component
function initializeComponent() {
const container = document.getElementById('search-container');
// Create the custom element
searchElement = document.createElement('gl-search');
// Set required attributes
searchElement.setAttribute('environment', 'development');
searchElement.setAttribute('jwt', 'your-jwt-token-here');
// Set optional attributes
searchElement.setAttribute('google_api_key', 'your-google-api-key');
searchElement.setAttribute('debounce_ms', '250');
searchElement.setAttribute('query', 'Location=New York&SearchTerm=pizza');
// Set data as JSON string
searchElement.setAttribute('data', JSON.stringify(searchData));
// Append to container
container.appendChild(searchElement);
console.log('Web Component initialized successfully');
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponent);
} else {
initializeComponent();
}
</script>
</body>
</html>
API Reference
Props / Attributes
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
environment | string | ✅ Yes | - | Environment mode: 'development', 'qa', 'staging' or 'production' |
jwt | string | ✅ Yes | - | JWT authentication token |
google_api_key | string | No | - | Google Maps API key for location autocomplete |
debounce_ms | number | No | 250 | Debounce time in milliseconds for search input |
query | string | No | - | Initial search query (format: 'Location=...&SearchTerm=...') |
auto-search | attribute | No | - | Present on <gl-search> to run query as a search on load. React prop: auto_search |
data | object | No | {} | Configuration object (see below) |
Data Object Structure
{
banner_color?: string; // Banner background color
banner_text?: string[]; // Array of rotating banner texts (supports HTML)
search_text?: Array<{ // Array of search placeholder suggestions
content: string; // Placeholder text (supports HTML)
key: string; // Search term key
}>;
search_term?: string; // Initial search term
search_button_color?: { // Search button styling
text: string; // Text color
background: string; // Background color
};
}
Ref Methods (React Only)
When using React, you can access component methods via ref:
interface GlSearchBarHandle {
focus: () => void; // Focus on the location input field
setQuery: (query: string) => void; // Set search query programmatically, without searching
search: () => void; // Run a search with whatever is in the inputs
setCardPrograms: (cardPrograms: string) => void; // Set card programs (defaults to 'restaurant;spa' if not set)
}
setQuery() only fills the inputs; call search() when you want results. The same two methods are exposed on
the <gl-search> element itself, so a page can drive it without React:
const search = document.querySelector('gl-search');
search.setQuery('Location=New York, NY&SearchTerm=pizza');
search.search();
To run the query straight away on load, add the auto-search attribute:
<gl-search jwt="..." environment="qa" query="Location=New York, NY&SearchTerm=pizza" auto-search></gl-search>
Example Usage
import React, { useRef } from 'react';
import { GlSearch } from '@gift-card-market/gl-search';
import type { GlSearchBarHandle } from '@gift-card-market/gl-search';
function App() {
const searchRef = useRef<GlSearchBarHandle>(null);
const handleFocus = () => {
searchRef.current?.focus();
};
const handleSetQuery = () => {
searchRef.current?.setQuery('Location=New York&SearchTerm=pizza');
};
const handleSetCardPrograms = () => {
searchRef.current?.setCardPrograms('spa;choose_your_own_burger');
};
return (
<div>
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleSetQuery}>Set Query</button>
<button onClick={handleSetCardPrograms}>Set Card Programs</button>
<GlSearch
ref={searchRef}
environment="development"
jwt="your-jwt-token"
/>
</div>
);
}
Vanilla JS Methods
In Vanilla JS, you can call methods directly on the DOM element:
const searchElement = document.querySelector('gl-search');
// Focus on input
searchElement.focus();
// Set query
searchElement.setQuery('Location=New York&SearchTerm=pizza');
// Set card programs
searchElement.setCardPrograms('spa;choose_your_own_burger');
Custom Events & Shadow DOM
Shadow DOM Isolation
The Web Component wrapper (gl-search) uses Shadow DOM (mode: 'open') to completely encapsulate styling. This prevents outside CSS styles from bleeding into the component and vice versa. Component styles are compiled and injected directly into the shadow root during build execution, so you do not need to link an external stylesheet for the Web Component.
The element can be moved around the page. On reconnect it reuses its existing shadow root and keeps rendering, so wrapping it in a tab, an accordion or a drag-and-drop layout is safe.
Custom Events
Custom DOM Events are dispatched once, on the component, and bubble up with composed: true, so a listener on the element, on document or on window each receive them exactly once:
| Event Name | Description | Event Detail |
|---|---|---|
gl:search | Dispatched when a search runs: the Search button, Enter in the search input, search(), or auto-search on load | { query: string, instanceId: string } |
search_bar.search | Global event dispatched for Search Results component | { data: { location, term, sort_by, provider_id, card_programs }, timestamp, instanceId } |
gl:clear | Dispatched when the search is cleared | { instanceId: string } |
Note: The
card_programsvalue defaults to'restaurant;spa'if not explicitly set via thesetCardPrograms()method.
Note:
instanceIdidentifies the component that raised the event, so a page with more than one search bar can tell them apart.
You can listen to these events directly on the Web Component instance (recommended):
const searchComponent = document.querySelector('gl-search');
searchComponent.addEventListener('gl:search', (e) => {
console.log('Search triggered with query:', e.detail.query);
});
Event Handling
React Event Handling
import React, { useEffect } from 'react';
import { GlSearch, SearchBarEvents } from '@gift-card-market/gl-search';
import '@gift-card-market/gl-search/dist/style.css';
function App() {
useEffect(() => {
// Listen to search event
const handleSearch = (e: CustomEvent) => {
console.log('Search performed:', e.detail);
// e.detail.query contains: "Location=...&SearchTerm=...&SortBy=...&ProviderId=..."
// Parse query string
const params = new URLSearchParams(e.detail.query);
const location = params.get('Location');
const searchTerm = params.get('SearchTerm');
console.log('Location:', location);
console.log('Search Term:', searchTerm);
// Perform your search logic here
// e.g., fetch data from API, update state, etc.
};
// Listen to clear event
const handleClear = (e: CustomEvent) => {
console.log('Search cleared:', e.detail);
// Reset your search results, state, etc.
};
// Add event listeners
document.addEventListener(SearchBarEvents.SEARCH, handleSearch as EventListener);
document.addEventListener(SearchBarEvents.CLEAR, handleClear as EventListener);
// Cleanup
return () => {
document.removeEventListener(SearchBarEvents.SEARCH, handleSearch as EventListener);
document.removeEventListener(SearchBarEvents.CLEAR, handleClear as EventListener);
};
}, []);
return (
<div>
<GlSearch
environment="development"
jwt="your-jwt-token"
/>
</div>
);
}
export default App;
Vanilla JS Event Handling
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GlSearch - Event Handling</title>
<!-- Include Web Component script (CSS is automatically injected via Shadow DOM) -->
<script src="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.global.js"></script>
</head>
<body>
<div id="search-container"></div>
<script>
// Initialize the component
function initializeComponent() {
const container = document.getElementById('search-container');
const searchElement = document.createElement('gl-search');
searchElement.setAttribute('environment', 'development');
searchElement.setAttribute('jwt', 'your-jwt-token');
container.appendChild(searchElement);
}
// Listen to search event
document.addEventListener('gl:search', function(e) {
console.log('Search performed:', e.detail);
// Parse query string
const params = new URLSearchParams(e.detail.query);
const location = params.get('Location');
const searchTerm = params.get('SearchTerm');
console.log('Location:', location);
console.log('Search Term:', searchTerm);
// Perform your search logic here
// e.g., fetch data from API, update UI, etc.
});
// Listen to clear event
document.addEventListener('gl:clear', function(e) {
console.log('Search cleared:', e.detail);
// Reset your search results, UI, etc.
});
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponent);
} else {
initializeComponent();
}
</script>
</body>
</html>
Complete Examples
React Complete Example
import React, { useRef, useEffect, useState } from 'react';
import { GlSearch, SearchBarEvents } from '@gift-card-market/gl-search';
import type { GlSearchBarHandle } from '@gift-card-market/gl-search';
import '@gift-card-market/gl-search/dist/style.css';
function App() {
const searchRef = useRef<GlSearchBarHandle>(null);
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
// Configuration
const searchData = {
banner_color: 'darkblue',
banner_text: [
`Do they love burgers? 🍔 Search for their <strong style="color: #ffe606">favorite</strong> below.`,
`You've got <strong style="color: #ffe606">great taste</strong>. We've got gift cards to match.`,
],
search_text: [
{
content: `(thinking <u>top smash burger joints</u>?)`,
key: 'smash burgers',
},
{
content: `(thinking <u>best lunch spots near me</u>?)`,
key: 'best lunch spots',
},
],
search_term: '',
search_button_color: {
text: 'yellow',
background: 'green',
},
};
useEffect(() => {
// Handle search event
const handleSearch = async (e: CustomEvent) => {
console.log('Search event:', e.detail);
setLoading(true);
try {
// Parse query parameters
const params = new URLSearchParams(e.detail.query);
const location = params.get('Location');
const searchTerm = params.get('SearchTerm');
// Call your API
const response = await fetch(`/api/search?location=${location}&term=${searchTerm}`);
const data = await response.json();
setSearchResults(data.results);
} catch (error) {
console.error('Search error:', error);
} finally {
setLoading(false);
}
};
// Handle clear event
const handleClear = (e: CustomEvent) => {
console.log('Clear event:', e.detail);
setSearchResults([]);
};
// Add listeners
document.addEventListener(SearchBarEvents.SEARCH, handleSearch as EventListener);
document.addEventListener(SearchBarEvents.CLEAR, handleClear as EventListener);
// Cleanup
return () => {
document.removeEventListener(SearchBarEvents.SEARCH, handleSearch as EventListener);
document.removeEventListener(SearchBarEvents.CLEAR, handleClear as EventListener);
};
}, []);
// Handler functions for buttons
const handleFocus = () => {
searchRef.current?.focus();
};
const handleSetQuery = () => {
searchRef.current?.setQuery('Location=New York&SearchTerm=pizza');
};
return (
<div className="app">
{/* Control buttons */}
<div style={{ padding: '20px', background: '#f5f5f5' }}>
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleSetQuery}>Set Query (NY/Pizza)</button>
</div>
{/* Search component */}
<GlSearch
ref={searchRef}
environment="development"
jwt="your-jwt-token"
google_api_key="your-google-api-key"
data={searchData}
debounce_ms={250}
/>
{/* Results */}
{loading && <div>Loading...</div>}
{searchResults.length > 0 && (
<div className="results">
{searchResults.map((result, index) => (
<div key={index}>{/* Render your results */}</div>
))}
</div>
)}
</div>
);
}
export default App;
Vanilla JS Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GlSearch - Complete Vanilla JS Example</title>
<!-- Include Web Component script (CSS is automatically injected via Shadow DOM) -->
<script src="../../node_modules/@gift-card-market/gl-search/dist/browser/gl-search.global.js"></script>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.control-panel {
padding: 20px;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
}
.control-panel button {
margin-right: 10px;
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.results {
padding: 20px;
}
.loading {
text-align: center;
padding: 20px;
font-size: 18px;
}
</style>
</head>
<body>
<!-- Control Panel -->
<div class="control-panel">
<button onclick="handleFocus()">🎯 Focus Input</button>
<button onclick="handleSetQuery()">📝 Set Query (NY/Pizza)</button>
</div>
<!-- Search Container -->
<div id="search-container"></div>
<!-- Results Container -->
<div id="results-container"></div>
<script>
// Configuration
const searchData = {
banner_color: 'darkblue',
banner_text: [
`Do they love burgers? 🍔 Search for their <strong style="color: #ffe606">favorite</strong> below.`,
`You've got <strong style="color: #ffe606">great taste</strong>. We've got gift cards to match.`,
],
search_text: [
{
content: `(thinking <u>top smash burger joints</u>?)`,
key: 'smash burgers',
},
{
content: `(thinking <u>best lunch spots near me</u>?)`,
key: 'best lunch spots',
},
],
search_term: '',
search_button_color: {
text: 'yellow',
background: 'green',
},
};
// Store reference to the web component
let searchElement = null;
// Initialize the web component
function initializeComponent() {
const container = document.getElementById('search-container');
// Create the custom element
searchElement = document.createElement('gl-search');
// Set required attributes
searchElement.setAttribute('environment', 'development');
searchElement.setAttribute('jwt', 'your-jwt-token');
// Set optional attributes
searchElement.setAttribute('google_api_key', 'your-google-api-key');
searchElement.setAttribute('debounce_ms', '250');
searchElement.setAttribute('data', JSON.stringify(searchData));
// Append to container
container.appendChild(searchElement);
console.log('Component initialized');
}
// Handle search event
document.addEventListener('gl:search', async function(e) {
console.log('Search performed:', e.detail);
const resultsContainer = document.getElementById('results-container');
resultsContainer.innerHTML = '<div class="loading">Loading...</div>';
try {
// Parse query parameters
const params = new URLSearchParams(e.detail.query);
const location = params.get('Location');
const searchTerm = params.get('SearchTerm');
console.log('Location:', location);
console.log('Search Term:', searchTerm);
// Call your API
const response = await fetch(`/api/search?location=${location}&term=${searchTerm}`);
const data = await response.json();
// Display results
resultsContainer.innerHTML = '<div class="results"><h2>Search Results</h2>' +
data.results.map(result => `<div>${result.name}</div>`).join('') +
'</div>';
} catch (error) {
console.error('Search error:', error);
resultsContainer.innerHTML = '<div class="results">Error loading results</div>';
}
});
// Handle clear event
document.addEventListener('gl:clear', function(e) {
console.log('Search cleared:', e.detail);
document.getElementById('results-container').innerHTML = '';
});
// Method to focus input
function handleFocus() {
if (searchElement) {
searchElement.focus();
console.log('Input focused');
}
}
// Method to set query
function handleSetQuery() {
if (searchElement) {
searchElement.setQuery('Location=New York&SearchTerm=pizza');
console.log('Query set');
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponent);
} else {
initializeComponent();
}
</script>
</body>
</html>
TypeScript Support
This package includes full TypeScript definitions. Import types as needed:
import type {
GlSearchProps,
GlSearchBarHandle,
SearchBarEventType
} from '@gift-card-market/gl-search';
import { SearchBarEvents } from '@gift-card-market/gl-search';
Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
License
ISC © Gift Card Market
Support
For issues and questions, please visit:
Contributing
Contributions are welcome! Please refer to the project's contributing guidelines.
Stylesheets: which file goes where
- Web Component (
dist/browser/gl-search.global.js): add no stylesheet to the page. The styles are inlined into the shadow root, so nothing is needed at document level. - React: import
@gift-card-market/gl-search/style.css. Every rule in it is prefixed with.gl-search-scope, so importing it cannot change the width, margin or any other style of a host-page class such as Bootstrap's.container. dist/browser/gl-search.css,dist/web.cssanddist/index.csscarry the same scoped rules for existing integrations. They are not needed when you use the.global.jsbundle.
Changelog
Version 1.1.0 (Current)
- 🆕
search()method andauto-searchattribute (auto_searchprop). - 📢
gl:search,gl:clearandsearch_bar.searchcarry aninstanceId. - 🔒 Shipped stylesheets are fully scoped to
.gl-search-scope; search button colors no longer touch<html>. - 🐛 Moving the element no longer throws; debug
console.logcalls removed. - 🔍 Google Places autocomplete failures are logged with
console.warnand Google's reason.
Version 1.0.18
- 🔒 Encapsulated CSS bleeding fixes (scoped variables and nested selectors).
- ⚙️ Added Tailwind utility prefix and configured important option.
Version 1.0.16
- ✨ Added support for pressing Enter in the search term field to immediately trigger a precise search using the active location.
- 🔒 Enabled complete Shadow DOM encapsulation to protect styling from outside layout bleeding.
- 📢 Custom events now bubble up and dispatch directly on the custom element host wrapper.