{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "onboarding-checklist",
  "title": "Onboarding Checklist",
  "type": "registry:block",
  "description": "Progressive activation checklist card: header with title, an N of M complete counter and a linear Progress bar, tappable task rows with icon tiles, one-line descriptions and chevrons (completed tasks flip to a filled check tile with muted copy), a footer with a Skip for now ghost link and an X dismiss that fades the card out, and a success panel with a glowing check circle at 5 of 5. State is local — wire tasks to your own activation API.",
  "categories": [
    "auth",
    "app",
    "onboarding"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/onboarding-checklist/onboarding-checklist.ts",
      "content": "import { LitElement, css, html } from 'lit'\nimport { Bell, BookOpen, Building2, Check, ChevronRight, Database, Users, X } 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/button/button'\nimport '../../components/card/card'\nimport '../../components/progress/progress'\n\ninterface ChecklistTask {\n  id: string\n  heading: string\n  description: string\n  iconName: string\n  done: boolean\n}\n\n/**\n * <uip-onboarding-checklist> — the registry OnboardingChecklist block as a web component.\n *\n * Class strings are React's OnboardingChecklist verbatim. React state becomes\n * @state fields. `initial-complete` seeds every task as done (React's\n * `initialComplete`). React's `className` maps to classes on the host (per\n * CONVENTIONS: layout + inherited styles go on the host). Task icons are\n * static per-row markup (lucide via icon()), not a component map.\n */\nexport class UipOnboardingChecklist 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    initialComplete: { type: Boolean, attribute: 'initial-complete' },\n    tasks: { state: true },\n    open: { state: true },\n  }\n\n  initialComplete = false\n  private tasks: ChecklistTask[] = []\n  private open = true\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', 'onboarding-checklist')\n    if (this.tasks.length === 0) {\n      const seeds = [\n        { id: 'workspace', heading: 'Create workspace', description: 'Name your team space and pick a URL.' },\n        { id: 'teammates', heading: 'Invite teammates', description: 'Bring in the people you work with.' },\n        { id: 'data', heading: 'Connect your data', description: 'Sync sources so insights flow in automatically.' },\n        { id: 'alerts', heading: 'Set up alerts', description: 'Get notified when metrics move.' },\n        { id: 'api', heading: 'Explore API docs', description: 'Learn the endpoints and ship your first request.' },\n      ]\n      this.tasks = seeds.map((t, i) => ({ ...t, iconName: t.id, done: this.initialComplete || i < 3 }))\n    }\n  }\n\n  private toggle(id: string) {\n    this.tasks = this.tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t))\n  }\n\n  private taskIcon(id: string, cls: string) {\n    switch (id) {\n      case 'workspace':\n        return icon(Building2, 'building-2', cls)\n      case 'teammates':\n        return icon(Users, 'users', cls)\n      case 'data':\n        return icon(Database, 'database', cls)\n      case 'alerts':\n        return icon(Bell, 'bell', cls)\n      default:\n        return icon(BookOpen, 'book-open', cls)\n    }\n  }\n\n  render() {\n    const completed = this.tasks.filter((t) => t.done).length\n    const total = this.tasks.length\n    const percent = total === 0 ? 0 : Math.round((completed / total) * 100)\n    const allDone = total > 0 && completed === total\n    return html`\n      <div\n        data-slot=\"onboarding-checklist\"\n        class=${cn(\n          'mx-auto w-full max-w-md transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none',\n          !this.open && 'pointer-events-none translate-y-1 opacity-0',\n        )}\n      >\n        <uip-card>\n          <uip-card-header>\n            <uip-card-title>Get started</uip-card-title>\n            <uip-card-description>Finish these steps to unlock everything your workspace can do.</uip-card-description>\n          </uip-card-header>\n          <uip-card-content class=\"space-y-4\">\n            <div class=\"space-y-2\">\n              <div class=\"flex items-center justify-between\">\n                <p class=\"text-muted-foreground text-xs\">\n                  <span class=\"text-foreground font-medium\"> ${completed} of ${total} </span> complete\n                </p>\n                <p class=\"text-muted-foreground text-xs tabular-nums\">${percent}%</p>\n              </div>\n              <uip-progress value=${percent}></uip-progress>\n            </div>\n\n            ${allDone\n              ? html`\n                  <div class=\"space-y-4 py-6 text-center\">\n                    <div\n                      class=\"border-success/30 bg-success/10 text-success mx-auto flex size-16 items-center justify-center rounded-full border shadow-xs\"\n                    >\n                      ${icon(Check, 'check', 'size-8')}\n                    </div>\n                    <div>\n                      <p class=\"text-lg font-semibold\">You're all set</p>\n                      <p class=\"text-muted-foreground mt-1 text-sm\">\n                        Every step is complete — your workspace is ready to go.\n                      </p>\n                    </div>\n                  </div>\n                `\n              : html`\n                  <div class=\"-mx-2 divide-y\">\n                    ${this.tasks.map(\n                      (task) => html`\n                        <button\n                          type=\"button\"\n                          role=\"checkbox\"\n                          aria-checked=${task.done ? 'true' : 'false'}\n                          @click=${() => this.toggle(task.id)}\n                          class=\"group hover:bg-muted/50 focus-visible:ring-ring flex w-full items-center gap-3 rounded-lg p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                        >\n                          ${task.done\n                            ? html`<span\n                                class=\"bg-primary text-primary-foreground flex size-9 shrink-0 items-center justify-center rounded-md shadow-xs\"\n                              >\n                                ${icon(Check, 'check', 'size-4')}\n                              </span>`\n                            : html`<span\n                                class=\"border-border bg-muted text-muted-foreground group-hover:bg-background group-hover:text-foreground flex size-9 shrink-0 items-center justify-center rounded-md border transition-colors\"\n                              >\n                                ${this.taskIcon(task.id, 'size-4')}\n                              </span>`}\n                          <span class=\"min-w-0 flex-1\">\n                            <span class=${cn('block truncate text-sm font-medium', task.done && 'text-muted-foreground')}>\n                              ${task.heading}\n                            </span>\n                            <span class=\"text-muted-foreground mt-0.5 block truncate text-xs\"\n                              >${task.description}</span\n                            >\n                          </span>\n                          ${icon(ChevronRight, 'chevron-right', 'text-muted-foreground/50 size-4 shrink-0 transition-transform duration-200 group-hover:translate-x-0.5')}\n                        </button>\n                      `,\n                    )}\n                  </div>\n                `}\n          </uip-card-content>\n          <uip-card-footer class=\"justify-between\">\n            ${!allDone\n              ? html`<uip-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  @click=${() => {\n                    this.open = false\n                  }}\n                  >Skip for now</uip-button\n                >`\n              : html`<span aria-hidden=\"true\"></span>`}\n            <button\n              type=\"button\"\n              aria-label=\"Dismiss checklist\"\n              @click=${() => {\n                this.open = false\n              }}\n              class=\"hover:bg-foreground/10 focus-visible:ring-ring rounded-md p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n            >\n              ${icon(X, 'x', 'text-muted-foreground size-4')}\n            </button>\n          </uip-card-footer>\n        </uip-card>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-onboarding-checklist') || customElements.define('uip-onboarding-checklist', UipOnboardingChecklist)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-onboarding-checklist': UipOnboardingChecklist\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/onboarding-checklist/onboarding-checklist.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/card.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/progress.json"
  ]
}
