Skip to main content

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.

Version License

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​

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​

PropertyTypeRequiredDefaultDescription
environmentstring✅ Yes-Environment mode: 'development', 'qa', 'staging' or 'production'
jwtstring✅ Yes-JWT authentication token
google_api_keystringNo-Google Maps API key for location autocomplete
debounce_msnumberNo250Debounce time in milliseconds for search input
querystringNo-Initial search query (format: 'Location=...&SearchTerm=...')
auto-searchattributeNo-Present on <gl-search> to run query as a search on load. React prop: auto_search
dataobjectNo{}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 NameDescriptionEvent Detail
gl:searchDispatched when a search runs: the Search button, Enter in the search input, search(), or auto-search on load{ query: string, instanceId: string }
search_bar.searchGlobal event dispatched for Search Results component{ data: { location, term, sort_by, provider_id, card_programs }, timestamp, instanceId }
gl:clearDispatched when the search is cleared{ instanceId: string }

Note: The card_programs value defaults to 'restaurant;spa' if not explicitly set via the setCardPrograms() method.

Note: instanceId identifies 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.css and dist/index.css carry the same scoped rules for existing integrations. They are not needed when you use the .global.js bundle.

Changelog​

Version 1.1.0 (Current)​

  • 🆕 search() method and auto-search attribute (auto_search prop).
  • 📢 gl:search, gl:clear and search_bar.search carry an instanceId.
  • 🔒 Shipped stylesheets are fully scoped to .gl-search-scope; search button colors no longer touch <html>.
  • 🐛 Moving the element no longer throws; debug console.log calls removed.
  • 🔍 Google Places autocomplete failures are logged with console.warn and 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.