@giftomatic/smart-search-box
v1.4.0
Published
A powerful, customizable web component for intelligent gift card autocomplete. Built with Preact and designed to work seamlessly with any framework or vanilla JavaScript.
Readme
Smart Search Box
A powerful, customizable web component for intelligent gift card autocomplete. Built with Preact and designed to work seamlessly with any framework or vanilla JavaScript.
Features
- 🔍 Smart Search - Intelligent autocomplete with real-time results
- 💳 Beautiful Display - Polished card layouts with responsive design
- ⚡ Lightweight - Minimal bundle size with optimal performance
- 🌐 Framework Agnostic - Works with React, Vue, Angular, or plain HTML
- 🔒 Type Safe - Built with TypeScript and Zod for robust validation
npm install @giftomatic/smart-search-boxQuick Start (Plain HTML)
<!DOCTYPE html>
<html>
<head>
<script src="https://jsdelivr.com/@giftomatic/smart-search-box"></script>
</head>
<input id="search-input-id" placeholder="Search for a gift card" />
<body>
<giftomatic-smart-search-box
for="search-input-id"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
></giftomatic-smart-search-box>
</body>
</html>With Frameworks
Installation
npm add @giftomatic/smart-search-box
yarn add @giftomatic/smart-search-boxReact/Preact
import "@giftomatic/smart-search-box";
function App() {
return (
<giftomatic-smart-search-box
for="search-input-id"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
/>
);
}Vue
<template>
<giftomatic-smart-search-box
for="search-input-id"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
/>
</template>
<script>
import "@giftomatic/smart-search-box";
export default {
name: "App",
};
</script>Angular
// app.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@giftomatic/smart-search-box";
@Component({
selector: "app-root",
template: `
<giftomatic-smart-search-box
for="search-input-id"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
></giftomatic-smart-search-box>
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {}Configuration
Attributes
The web component accepts the following attributes for configuration:
| Attribute | Type | Required | Description |
| -------------------- | ------ | -------- | ----------------------------------------------------------------- |
| for | string | Yes | ID of the <input> element to which the search box is associated |
| country | string | Yes | Two-letter ISO country code (e.g., "nl", "us", "de") |
| dataset | string | Yes | Dataset identifier for the gift card collection |
| partner-catalog-id | string | Yes | Partner catalog identifier for personalized results |
| endpoint | string | No | Backend API endpoint URL for gift card data. |
Example
<giftomatic-smart-search-box
endpoint="https://api.giftomatic.example.com"
country="us"
dataset="RETAIL_CARDS"
partner-catalog-id="12345"
></giftomatic-smart-search-box>Theming and Styling
The Smart Search Box exposes a comprehensive styling API through CSS Custom Properties and Shadow Parts, allowing you to customize the appearance to match your brand.
CSS Custom Properties
Override these properties on the host element to control sizing and typography:
giftomatic-smart-search-box {
/* Base font size for the component */
--ssb-base-font-size: 16px;
/* Maximum width of the dialog */
--ssb-max-width: 54rem;
/* Horizontal positioning offset */
--ssb-left-offset: 50%;
/* Font family for all text */
--ssb-font-family: "Lato", sans-serif;
}Available Custom Properties
--ssb-base-font-size: Base font size (default:1rem)--ssb-max-width: Dialog maximum width (default:54rem)--ssb-left-offset: Dialog horizontal position (default:50%)--ssb-font-family: Font family for dialog content (default: system fonts)
Derived Font Sizes
The component automatically calculates the following sizes based on --ssb-base-font-size:
--small-font-size: 75% of base--medium-font-size: 87.5% of base--large-font-size: 112.5% of base--extra-large-font-size: 125% of base
Shadow Parts
Use the ::part() selector to style specific elements within the component's shadow DOM:
/* Style the dialog container */
giftomatic-smart-search-box::part(dialog) {
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
/* Customize the search bar */
giftomatic-smart-search-box::part(searchbar) {
background-color: #f5f5f5;
border-bottom: 2px solid #e0e0e0;
}
/* Style the ribbon/badge on large cards */
giftomatic-smart-search-box::part(card-ribbon) {
background: linear-gradient(90deg, #ff7a18, #af002d 70%);
color: white;
font-weight: bold;
}
/* Customize CTA buttons */
giftomatic-smart-search-box::part(card-large-cta-button),
giftomatic-smart-search-box::part(card-small-cta-button) {
background-color: #007bff;
color: white;
border-radius: 8px;
}
/* Style pill components */
giftomatic-smart-search-box::part(pill) {
border-radius: 9999px;
background-color: #e3f2fd;
}Available Parts
| Part | Description |
| ----------------------- | ------------------------------------------------ |
| dialog | The main dialog wrapper element |
| searchbar | Search input container and submit button wrapper |
| main-content | Main content area below the search bar |
| card-large | Large/hero card root element |
| card-ribbon | Ribbon/label area in large card header |
| card-large-image | Image within the large card |
| card-large-cta-button | Primary CTA button in large card |
| card-small | Small card root element |
| card-small-image | Image within the small card |
| card-small-cta-button | CTA button in small card |
| pill | Pill/tag component root element |
Complete Theming Example
/* Define your brand colors and styles */
giftomatic-smart-search-box {
--ssb-base-font-size: 14px;
--ssb-max-width: 100%;
--ssb-font-family: "Inter", -apple-system, sans-serif;
}
/* Customize specific components */
giftomatic-smart-search-box::part(dialog) {
background: linear-gradient(to bottom, #ffffff, #f8f9fa);
border-radius: 16px;
}
giftomatic-smart-search-box::part(searchbar) {
padding: 1.5rem;
background-color: white;
}
giftomatic-smart-search-box::part(card-ribbon) {
background: #ff6b35;
text-transform: uppercase;
letter-spacing: 0.05em;
}
giftomatic-smart-search-box::part(card-large-cta-button) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 12px 24px;
font-weight: 600;
transition: transform 0.2s;
}
giftomatic-smart-search-box::part(card-large-cta-button):hover {
transform: translateY(-2px);
}SPA Routing Integration
For Single Page Applications (SPAs) that use client-side routing (React Router, Vue Router, Angular Router, etc.), the Smart Search Box provides a custom event system to handle navigation without full page reloads.
Event Details
The giftcard-navigate event provides:
event.detail = {
deeplink: string; // Deep link to the gift card page
giftcardName: string; // Display name of the gift card
reference: string; // A unique identifier for the gift card
mouseClick: MouseEvent; // Original event, e.g. to call preventDefault()
};Usage Examples
React Router
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import "@giftomatic/smart-search-box";
function App() {
const navigate = useNavigate();
function handleGiftcardNavigate(event) {
const { deeplink, giftcardName } = event.detail;
// Use your SPA router
// Extract ID from deeplink or navigate directly
navigate(`/giftcard/${deeplink}`, {
state: { name: giftcardName },
});
}
return (
<giftomatic-smart-search-box
endpoint="https://api.example.com"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
onGiftcardNavigate={handleGiftcardNavigate} // Requires React 19+
onClick={(e) => e.preventDefault()} // Prevent default navigation
/>
);
}Vue Router
<template>
<giftomatic-smart-search-box
ref="searchBoxRef"
endpoint="https://api.example.com"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
/>
</template>
<script>
import "@giftomatic/smart-search-box";
import { useRouter } from "vue-router";
import { onMounted, onBeforeUnmount, ref } from "vue";
export default {
name: "App",
setup() {
const router = useRouter();
const searchBoxRef = ref(null);
function handleGiftcardNavigate(event) {
const { deeplink, giftcardName } = event.detail;
// Use Vue Router
router.push({
path: deeplink,
query: { name: giftcardName },
});
}
function handleClick(event) {
event.preventDefault();
}
onMounted(() => {
const el = searchBoxRef.value;
if (!el) return;
el.addEventListener("giftcardNavigate", handleGiftcardNavigate);
el.addEventListener("click", handleClick);
});
onBeforeUnmount(() => {
const el = searchBoxRef.value;
if (!el) return;
el.removeEventListener("giftcardNavigate", handleGiftcardNavigate);
el.removeEventListener("click", handleClick);
});
return {};
},
};
</script>Angular Router
import {
Component,
OnInit,
OnDestroy,
ElementRef,
ViewChild,
} from "@angular/core";
import { Router } from "@angular/router";
import "@giftomatic/smart-search-box";
@Component({
selector: "app-root",
template: `
<giftomatic-smart-search-box
#searchBox
endpoint="https://api.example.com"
country="nl"
dataset="GIFTOMATIC"
partner-catalog-id="4"
></giftomatic-smart-search-box>
`,
})
export class AppComponent implements OnInit, OnDestroy {
@ViewChild("searchBox", { static: true })
searchBox!: ElementRef<HTMLElement>;
private giftcardNavigateHandler!: EventListener;
private clickHandler!: EventListener;
constructor(private router: Router) {}
ngOnInit() {
const el = this.searchBox.nativeElement;
this.giftcardNavigateHandler = (event: Event) => {
const { deeplink, giftcardName } = (event as CustomEvent).detail;
this.router.navigateByUrl(deeplink, {
state: { name: giftcardName },
});
};
this.clickHandler = (event: Event) => {
event.preventDefault();
};
el.addEventListener("giftcard-navigate", this.giftcardNavigateHandler);
el.addEventListener("click", this.clickHandler);
}
ngOnDestroy() {
const el = this.searchBox.nativeElement;
el.removeEventListener("giftcard-navigate", this.giftcardNavigateHandler);
el.removeEventListener("click", this.clickHandler);
}
}Vanilla JavaScript
import "@giftomatic/smart-search-box";
const searchBox = document.querySelector("giftomatic-smart-search-box");
// Listen for navigation events
searchBox.addEventListener("giftcard-navigate", (event) => {
const { deeplink, giftcardName } = event.detail;
console.log(`User clicked on: ${giftcardName}`);
console.log(`Deeplink: ${deeplink}`);
// Handle navigation with your custom router
// e.g., History API:
history.pushState({ giftcardName }, giftcardName, deeplink);
});