GL Search
Version: 1.0.18 Package:
@gift-card-market/gl-search
Last Updated: July 1, 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
- 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=...') |
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
setCardPrograms: (cardPrograms: string) => void; // Set card programs (defaults to 'restaurant;spa' if not set)
}
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.
Custom Events
Custom DOM Events are dispatched directly on the custom element host instance and bubble up with composed: true:
| Event Name | Description | Event Detail |
|---|---|---|
gl:search | Dispatched when a search is performed (either by clicking Search or pressing Enter in the search input) | { query: string } |
search_bar.search | Global event dispatched for Search Results component | { data: { location, term, sort_by, provider_id, card_programs }, timestamp } |
gl:clear | Dispatched when the search is cleared | {} |
Note: The
card_programsvalue defaults to'restaurant;spa'if not explicitly set via thesetCardPrograms()method.
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.
Changelog
Version 1.0.18 (Current)
- 🔒 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.