{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "onboarding-wizard",
  "title": "Onboarding Wizard",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-svelte/blocks/onboarding-wizard/OnboardingWizard.svelte",
      "content": "<script lang=\"ts\">\n  import { Check, ChevronLeft, ChevronRight, Globe, Plus, X } from '@lucide/svelte'\n  import { Badge } from '$lib/components/ui/badge'\n  import { Button } from '$lib/components/ui/button'\n  import { Input } from '$lib/components/ui/input'\n  import { SectionCard } from '$lib/components/ui/section-card'\n  import { Stepper, type StepperStepConfig } from '$lib/components/ui/stepper'\n  import { Switch } from '$lib/components/ui/switch'\n\n  interface Preference {\n    id: string\n    label: string\n    description: string\n    enabled: boolean\n  }\n\n  interface OnboardingWizardProps {\n    /** Jump straight to a step (1 Workspace · 2 Team · 3 Preferences · 4 Done). */\n    initialStep?: number\n    /** Pre-fill the workspace name so later steps read naturally in isolation. */\n    initialWorkspaceName?: string\n    /** Pre-seed invited teammates. */\n    initialInvites?: string[]\n    class?: string\n  }\n\n  let { initialStep = 1, initialWorkspaceName = 'Acme Inc', initialInvites = [], class: className }: OnboardingWizardProps =\n    $props()\n\n  const steps: StepperStepConfig[] = [\n    { id: 1, title: 'Workspace' },\n    { id: 2, title: 'Team' },\n    { id: 3, title: 'Preferences' },\n    { id: 4, title: 'Done' },\n  ]\n\n  // svelte-ignore state_referenced_locally: initial* props seed local state once by design.\n  let step = $state(initialStep)\n  // svelte-ignore state_referenced_locally: initial* props seed local state once by design.\n  let workspaceName = $state(initialWorkspaceName)\n  // svelte-ignore state_referenced_locally: initial* props seed local state once by design.\n  let invites = $state<string[]>([...initialInvites])\n  let inviteDraft = $state('')\n  let inviteInvalid = $state(false)\n\n  let preferences = $state<Preference[]>([\n    {\n      id: 'digests',\n      label: 'Email digests',\n      description: 'A daily summary of workspace activity.',\n      enabled: true,\n    },\n    {\n      id: 'updates',\n      label: 'Product updates',\n      description: 'Occasional notes about new features and improvements.',\n      enabled: false,\n    },\n    {\n      id: 'reports',\n      label: 'Weekly reports',\n      description: 'Usage analytics delivered every Monday morning.',\n      enabled: true,\n    },\n  ])\n\n  const slug = $derived.by(() => {\n    const cleaned = workspaceName.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')\n    return cleaned || 'your-workspace'\n  })\n\n  // The stepper only ever emits backward navigation (completed steps are the\n  // clickable ones); still, lock the header once the flow is Done so a stray\n  // click can't move the indicator while the success panel is showing.\n  const stepperSteps = $derived(steps.map((s) => ({ ...s, disabled: step === 4 })))\n\n  function addInvite() {\n    const email = inviteDraft.trim()\n    if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) || invites.includes(email)) {\n      inviteInvalid = true\n      return\n    }\n    inviteInvalid = false\n    invites.push(email)\n    inviteDraft = ''\n  }\n\n  function removeInvite(email: string) {\n    invites = invites.filter((e) => e !== email)\n  }\n\n  function onStepperInput(value: number) {\n    // Free backward navigation; forward goes through the footer button only.\n    if (value < step && step !== 4) step = value\n  }\n\n  function goNext() {\n    if (step < 4) step += 1\n  }\n\n  function reset() {\n    step = 1\n    workspaceName = ''\n    invites = []\n    inviteDraft = ''\n  }\n</script>\n\n<SectionCard\n  data-slot=\"onboarding-wizard\"\n  title=\"Set up your workspace\"\n  description=\"Four quick steps and your team is ready to collaborate.\"\n  class={className}\n>\n  <Stepper steps={stepperSteps} value={step} class=\"mb-6\" onValueChange={onStepperInput} />\n\n  <!-- Step 1 · Workspace -->\n  {#if step === 1}\n    <div class=\"space-y-4\">\n      <div class=\"space-y-2\">\n        <label for=\"onboarding-workspace-name\" class=\"text-sm font-medium\">Workspace name</label>\n        <Input\n          id=\"onboarding-workspace-name\"\n          bind:value={workspaceName}\n          placeholder=\"Acme Inc\"\n          autocomplete=\"off\"\n        />\n      </div>\n      <div class=\"border-border bg-muted/40 flex items-center gap-2 rounded-md border px-3 py-2\">\n        <Globe class=\"text-muted-foreground size-4 shrink-0\" aria-hidden=\"true\" />\n        <p class=\"min-w-0 truncate text-xs\">\n          <span class=\"text-muted-foreground\">uipkge.dev/</span>\n          <span class=\"text-foreground font-medium\">{slug}</span>\n        </p>\n      </div>\n    </div>\n  {:else if step === 2}\n    <!-- Step 2 · Team -->\n    <div class=\"space-y-4\">\n      <form\n        class=\"flex gap-2\"\n        onsubmit={(e) => {\n          e.preventDefault()\n          addInvite()\n        }}\n      >\n        <Input\n          bind:value={inviteDraft}\n          type=\"email\"\n          placeholder=\"teammate@company.com\"\n          status={inviteInvalid ? 'error' : undefined}\n          class=\"flex-1\"\n          onValueChange={() => (inviteInvalid = false)}\n        />\n        <Button type=\"submit\" variant=\"outline\" size=\"sm\" class=\"shrink-0\">\n          <Plus aria-hidden=\"true\" />\n          Add\n        </Button>\n      </form>\n      {#if inviteInvalid}\n        <p role=\"alert\" class=\"text-destructive text-xs\">Enter a valid email address.</p>\n      {/if}\n      {#if invites.length > 0}\n        <div class=\"flex flex-wrap gap-2\">\n          {#each invites as email (email)}\n            <Badge variant=\"secondary\" class=\"gap-1 py-1 pr-1 pl-2.5\">\n              {email}\n              <button\n                type=\"button\"\n                aria-label={`Remove ${email}`}\n                class=\"hover:bg-foreground/10 focus-visible:ring-ring min-h-6 rounded-full p-0.5 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                onclick={() => removeInvite(email)}\n              >\n                <X class=\"size-3\" aria-hidden=\"true\" />\n              </button>\n            </Badge>\n          {/each}\n        </div>\n      {:else}\n        <p class=\"text-muted-foreground text-xs\">No teammates invited yet.</p>\n      {/if}\n      <button\n        type=\"button\"\n        class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring min-h-6 text-xs underline-offset-4 transition-colors hover:underline focus-visible:ring-2 focus-visible:outline-none\"\n        onclick={goNext}\n      >\n        Skip for now\n      </button>\n    </div>\n  {:else if step === 3}\n    <!-- Step 3 · Preferences -->\n    <div class=\"divide-y rounded-lg border\">\n      {#each preferences as pref (pref.id)}\n        <div class=\"flex items-center justify-between gap-4 p-4\">\n          <div class=\"min-w-0\">\n            <p class=\"text-sm font-medium\">{pref.label}</p>\n            <p class=\"text-muted-foreground mt-0.5 text-xs\">{pref.description}</p>\n          </div>\n          <Switch bind:checked={pref.enabled} aria-label={pref.label} />\n        </div>\n      {/each}\n    </div>\n  {:else}\n    <!-- Step 4 · Done -->\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        <Check class=\"size-8\" aria-hidden=\"true\" />\n      </div>\n      <div>\n        <p class=\"text-lg font-semibold\">You're all set</p>\n        <p class=\"text-muted-foreground mx-auto mt-1 max-w-sm text-sm\">\n          {#if invites.length > 0}\n            {workspaceName || 'Your workspace'} is ready and {invites.length} invitation{invites.length === 1\n              ? ''\n              : 's'} sent.\n          {:else}\n            {workspaceName || 'Your workspace'} is ready — invite teammates anytime from settings.\n          {/if}\n        </p>\n      </div>\n      <Button variant=\"outline\" size=\"sm\" onclick={reset}>Set up another workspace</Button>\n    </div>\n  {/if}\n\n  {#snippet footer()}\n    <div class=\"flex w-full items-center justify-between\">\n      {#if step > 1 && step < 4}\n        <Button variant=\"ghost\" size=\"sm\" onclick={() => (step -= 1)}>\n          <ChevronLeft aria-hidden=\"true\" />\n          Back\n        </Button>\n      {:else}\n        <span aria-hidden=\"true\"></span>\n      {/if}\n      <span class=\"text-muted-foreground text-xs\">Step {step} of 4</span>\n      {#if step < 4}\n        <Button size=\"sm\" disabled={step === 1 && workspaceName.trim() === ''} onclick={goNext}>\n          {step === 3 ? 'Finish' : 'Continue'}\n          <ChevronRight aria-hidden=\"true\" />\n        </Button>\n      {:else}\n        <span aria-hidden=\"true\"></span>\n      {/if}\n    </div>\n  {/snippet}\n</SectionCard>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/onboarding-wizard/OnboardingWizard.svelte"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/badge.json",
    "https://uipkge.dev/r/svelte/button.json",
    "https://uipkge.dev/r/svelte/input.json",
    "https://uipkge.dev/r/svelte/section-card.json",
    "https://uipkge.dev/r/svelte/stepper.json",
    "https://uipkge.dev/r/svelte/switch.json"
  ],
  "description": "Multi-step workspace setup flow in a SectionCard: a four-step Stepper (Workspace → Team → Preferences → Done) with check icons on completed steps, a workspace-name input with live URL slug preview, teammate invites as removable email chips with a skip link, preference Switch rows, and a success panel with a glowing check circle. Footer carries Back/Continue actions plus a Step X of 4 counter. Data is stubbed inline — wire your API of choice.",
  "categories": [
    "auth",
    "app",
    "onboarding"
  ]
}