{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "search-results-page",
  "title": "Search Results Page",
  "type": "registry:page",
  "description": "Full search results page: large pre-filled search input with result count, removable filter chips with clear-all, a left facet sidebar with Type and Team checkbox groups (hidden below lg), six result rows with icon tiles, titles, paths, snippets, and type/updated meta, plus a Previous/Next pagination footer. Chips and facets are interactive; swap the stub rows for your search source.",
  "categories": [
    "layout",
    "app"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/search-results-page/search-results-page.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { BookOpen, FileText, GitBranch, MessageSquare, Search, Users, Video, type IconNode } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/badge/badge'\nimport '../../components/button/button'\nimport '../../components/checkbox/checkbox'\nimport '../../components/chip/chip'\nimport '../../components/input/input'\nimport type { UipInput } from '../../components/input/input'\n\ninterface SearchResult {\n  id: string\n  icon: IconNode\n  iconName: string\n  heading: string\n  path: string\n  snippet: string\n  type: string\n  updated: string\n}\n\ninterface FacetOption {\n  id: string\n  label: string\n  count: number\n}\n\ninterface FacetGroup {\n  id: string\n  label: string\n  options: FacetOption[]\n}\n\ninterface Chip {\n  id: string\n  label: string\n}\n\nconst RESULTS: SearchResult[] = [\n  {\n    id: 'r1',\n    icon: BookOpen,\n    iconName: 'book-open',\n    heading: 'Onboarding flows: design your first-run experience',\n    path: 'docs.acme.dev / guides / onboarding-flows',\n    snippet:\n      'Learn how to compose onboarding flows from checklists, tours, and progressive disclosure so new users reach their first aha moment faster.',\n    type: 'Docs',\n    updated: '2 days ago',\n  },\n  {\n    id: 'r2',\n    icon: FileText,\n    iconName: 'file-text',\n    heading: 'Checklist API reference for onboarding flows',\n    path: 'docs.acme.dev / api / checklist',\n    snippet:\n      'Full reference for the Checklist primitive used to drive onboarding flows, including step completion events, persistence, and theming.',\n    type: 'Reference',\n    updated: '5 hours ago',\n  },\n  {\n    id: 'r3',\n    icon: Video,\n    iconName: 'video',\n    heading: 'Video walkthrough: building onboarding flows in 20 minutes',\n    path: 'learn.acme.dev / videos / onboarding-flows-walkthrough',\n    snippet:\n      'A hands-on screencast that builds a complete onboarding flow with branching steps, skippable tours, and analytics instrumentation.',\n    type: 'Video',\n    updated: '1 week ago',\n  },\n  {\n    id: 'r4',\n    icon: MessageSquare,\n    iconName: 'message-square',\n    heading: 'How do you measure completion of onboarding flows?',\n    path: 'community.acme.dev / discussions / 4821',\n    snippet:\n      'Community thread comparing activation-rate funnels, step-drop-off charts, and qualitative surveys for measuring onboarding flow success.',\n    type: 'Discussion',\n    updated: '3 days ago',\n  },\n  {\n    id: 'r5',\n    icon: GitBranch,\n    iconName: 'git-branch',\n    heading: 'Example repo: onboarding flows with A/B-tested variants',\n    path: 'github.com / acme / examples / onboarding-ab-test',\n    snippet:\n      'Runnable example that splits new signups into two onboarding flow variants and reports which one converts better to first project creation.',\n    type: 'Example',\n    updated: '2 weeks ago',\n  },\n  {\n    id: 'r6',\n    icon: Users,\n    iconName: 'users',\n    heading: 'Playbook: personalizing onboarding flows by team role',\n    path: 'docs.acme.dev / playbooks / role-based-onboarding',\n    snippet:\n      'Patterns for tailoring onboarding flows to admins, editors, and viewers, with sample copy, suggested defaults, and rollout checklists.',\n    type: 'Guide',\n    updated: '4 days ago',\n  },\n]\n\nconst FACET_GROUPS: FacetGroup[] = [\n  {\n    id: 'type',\n    label: 'Type',\n    options: [\n      { id: 'type-docs', label: 'Docs', count: 18 },\n      { id: 'type-guides', label: 'Guides', count: 11 },\n      { id: 'type-video', label: 'Video', count: 7 },\n      { id: 'type-discussion', label: 'Discussion', count: 6 },\n    ],\n  },\n  {\n    id: 'team',\n    label: 'Team',\n    options: [\n      { id: 'team-product', label: 'Product', count: 14 },\n      { id: 'team-engineering', label: 'Engineering', count: 16 },\n      { id: 'team-design', label: 'Design', count: 8 },\n      { id: 'team-support', label: 'Support', count: 4 },\n    ],\n  },\n]\n\nconst TOTAL_PAGES = 3\n\n/**\n * <uip-search-results-page> — the registry SearchResultsPage block as a web\n * component.\n *\n * Class strings are React's SearchResultsPage verbatim. React state (query,\n * chips, facets, page) becomes @state fields. No props, no events: swap the\n * stub rows for your search source.\n *\n * Deviations: result `title` is `heading` in the internal row type only (no\n * public prop, no rename needed). The search field icon moves from React's\n * absolute overlay into <uip-input>'s prefix slot (same visual result; the\n * input's `pl-9` is dropped since the slot provides the gutter).\n */\nexport class UipSearchResultsPage extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    query: { state: true },\n    chips: { state: true },\n    activeFacets: { state: true },\n    page: { state: true },\n  }\n\n  private query = 'onboarding flows'\n  private chips: Chip[] = [\n    { id: 'type-docs', label: 'Type: Docs' },\n    { id: 'team-product', label: 'Team: Product' },\n  ]\n  private activeFacets: Set<string> = new Set(['type-docs', 'team-product'])\n  private page = 1\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'search-results-page')\n  }\n\n  private removeChip(id: string) {\n    this.chips = this.chips.filter((chip) => chip.id !== id)\n  }\n\n  private toggleFacet(id: string, checked: boolean) {\n    const next = new Set(this.activeFacets)\n    if (checked) next.add(id)\n    else next.delete(id)\n    this.activeFacets = next\n  }\n\n  render() {\n    const start = (this.page - 1) * RESULTS.length + 1\n    const resultRange = `${start}-${start + RESULTS.length - 1}`\n    return html`\n      <div data-slot=\"search-results-page\" class=${cn('w-full space-y-6')}>\n        <div class=\"space-y-3\">\n          <div class=\"relative max-w-xl\">\n            <uip-input\n              type=\"search\"\n              class=\"[&::part(control)]:h-11 text-base\"\n              .value=${this.query}\n              placeholder=\"Search...\"\n              @input=${(e: Event) => {\n                this.query = (e.target as UipInput).value\n              }}\n            >\n              <span slot=\"prefix\">${icon(Search, 'search', 'size-4')}</span>\n            </uip-input>\n          </div>\n          <p class=\"text-muted-foreground text-sm\">${resultRange} of 42 results for &ldquo;${this.query}&rdquo;</p>\n        </div>\n\n        ${this.chips.length > 0\n          ? html`<div class=\"flex flex-wrap items-center gap-2\">\n              ${this.chips.map(\n                (chip) => html`\n                  <uip-chip variant=\"outline\" size=\"sm\" closable @close=${() => this.removeChip(chip.id)}>\n                    ${chip.label}\n                  </uip-chip>\n                `,\n              )}\n              <uip-button\n                variant=\"link\"\n                size=\"sm\"\n                class=\"text-muted-foreground h-auto min-h-6 px-2\"\n                @click=${() => (this.chips = [])}\n              >\n                Clear all\n              </uip-button>\n            </div>`\n          : nothing}\n\n        <div class=\"flex gap-8\">\n          <aside class=\"hidden w-48 shrink-0 space-y-6 lg:block\" aria-label=\"Filters\">\n            ${FACET_GROUPS.map(\n              (group) => html`\n                <div>\n                  <h3 class=\"text-muted-foreground mb-3 text-xs font-medium tracking-wider uppercase\">\n                    ${group.label}\n                  </h3>\n                  <ul class=\"space-y-2.5\">\n                    ${group.options.map(\n                      (option) => html`\n                        <li>\n                          <label\n                            for=${`${group.id}-${option.id}`}\n                            class=\"flex cursor-pointer items-center gap-2 text-sm\"\n                          >\n                            <uip-checkbox\n                              id=${`${group.id}-${option.id}`}\n                              .checked=${this.activeFacets.has(option.id)}\n                              @checked-change=${(e: CustomEvent<{ checked: boolean }>) =>\n                                this.toggleFacet(option.id, e.detail.checked)}\n                            ></uip-checkbox>\n                            <span>${option.label}</span>\n                            <span class=\"text-muted-foreground ml-auto text-xs tabular-nums\">${option.count}</span>\n                          </label>\n                        </li>\n                      `,\n                    )}\n                  </ul>\n                </div>\n              `,\n            )}\n          </aside>\n\n          <div class=\"min-w-0 flex-1\">\n            <ol class=\"divide-border divide-y\">\n              ${RESULTS.map(\n                (result) => html`\n                  <li>\n                    <a\n                      href=\"#\"\n                      class=\"hover:bg-muted/50 focus-visible:ring-ring block rounded-lg p-4 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                    >\n                      <div class=\"flex gap-4\">\n                        <div\n                          class=\"bg-muted/50 text-muted-foreground flex size-10 shrink-0 items-center justify-center rounded-md border\"\n                        >\n                          ${icon(result.icon, result.iconName, 'size-5')}\n                        </div>\n                        <div class=\"min-w-0 space-y-1\">\n                          <p class=\"truncate font-medium\">${result.heading}</p>\n                          <p class=\"text-muted-foreground truncate text-xs\">${result.path}</p>\n                          <p class=\"text-muted-foreground line-clamp-2 text-sm\">${result.snippet}</p>\n                          <div class=\"flex items-center gap-3 pt-1\">\n                            <uip-badge variant=\"secondary\">${result.type}</uip-badge>\n                            <span class=\"text-muted-foreground text-xs\">Updated ${result.updated}</span>\n                          </div>\n                        </div>\n                      </div>\n                    </a>\n                  </li>\n                `,\n              )}\n            </ol>\n\n            <nav class=\"flex items-center justify-between pt-4\" aria-label=\"Pagination\">\n              <uip-button variant=\"outline\" size=\"sm\" ?disabled=${this.page === 1} @click=${() => (this.page -= 1)}>\n                Previous\n              </uip-button>\n              <div class=\"flex items-center gap-1\">\n                ${Array.from({ length: TOTAL_PAGES }, (_, i) => i + 1).map(\n                  (n) => html`\n                    <uip-button\n                      variant=${n === this.page ? 'outline' : 'ghost'}\n                      size=\"icon-sm\"\n                      aria-current=${n === this.page ? 'page' : nothing}\n                      @click=${() => (this.page = n)}\n                    >\n                      ${n}\n                    </uip-button>\n                  `,\n                )}\n              </div>\n              <uip-button\n                variant=\"outline\"\n                size=\"sm\"\n                ?disabled=${this.page === TOTAL_PAGES}\n                @click=${() => (this.page += 1)}\n              >\n                Next\n              </uip-button>\n            </nav>\n          </div>\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-search-results-page') || customElements.define('uip-search-results-page', UipSearchResultsPage)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-search-results-page': UipSearchResultsPage\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/search-results-page/search-results-page.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/badge.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/checkbox.json",
    "https://uipkge.dev/r/lit/chip.json",
    "https://uipkge.dev/r/lit/input.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
