npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-box

Quick 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-box

React/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);
});