{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "terminal",
  "title": "Terminal",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/terminal/Terminal.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nexport interface TerminalLine {\n  /** Prompt prefix shown before the command. Omit for output-only lines. */\n  prompt?: string\n  /** Command text shown after the prompt. */\n  command?: string\n  /** Output lines rendered below the command. */\n  output?: string\n  /** Override the line type: 'command' renders prompt+command, 'output' renders plain text. */\n  type?: 'command' | 'output'\n}\n\ninterface Props {\n  /** Command history to render. */\n  lines: TerminalLine[]\n  /** Window title shown in the title bar. Default 'bash'. */\n  title?: string\n  /** Prompt character. Default '$'. */\n  promptChar?: string\n  /** Color theme. Default 'dark'. */\n  theme?: 'dark' | 'light'\n  /** Auto-scroll to bottom when new lines arrive. Default true. */\n  autoScroll?: boolean\n  /** Animate lines typing in one-by-one. Default false. */\n  typing?: boolean\n  /** Typing speed in ms per line. Default 120. */\n  typingSpeed?: number\n  /** Max height before scrolling. Default '400px'. */\n  maxHeight?: string\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  title: 'bash',\n  promptChar: '$',\n  theme: 'dark',\n  autoScroll: true,\n  typing: false,\n  typingSpeed: 120,\n  maxHeight: '400px',\n})\n\nconst bodyRef = ref<HTMLElement | null>(null)\nconst visibleCount = ref(props.typing ? 0 : props.lines.length)\n\nconst resolvedLines = computed(() =>\n  props.lines.map((l) => ({\n    ...l,\n    type: l.type ?? (l.prompt || l.command ? 'command' : 'output'),\n    prompt: l.prompt ?? (l.type === 'output' ? '' : props.promptChar),\n  })),\n)\n\nconst shownLines = computed(() => resolvedLines.value.slice(0, visibleCount.value))\n\nfunction scrollToBottom() {\n  if (!props.autoScroll || !bodyRef.value) return\n  nextTick(() => {\n    if (bodyRef.value) bodyRef.value.scrollTop = bodyRef.value.scrollHeight\n  })\n}\n\nwatch(\n  () => props.lines.length,\n  () => {\n    if (props.typing) {\n      // Reset typing animation when lines change.\n      visibleCount.value = 0\n      runTyping()\n    } else {\n      visibleCount.value = props.lines.length\n      scrollToBottom()\n    }\n  },\n)\n\nlet typingTimer: ReturnType<typeof setTimeout> | null = null\nfunction runTyping() {\n  if (typingTimer) clearTimeout(typingTimer)\n  const tick = () => {\n    if (visibleCount.value < props.lines.length) {\n      visibleCount.value++\n      scrollToBottom()\n      typingTimer = setTimeout(tick, props.typingSpeed)\n    }\n  }\n  typingTimer = setTimeout(tick, props.typingSpeed)\n}\n\nonMounted(() => {\n  if (props.typing) runTyping()\n  else scrollToBottom()\n})\n\nonBeforeUnmount(() => {\n  if (typingTimer) clearTimeout(typingTimer)\n})\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"terminal\"\n    :data-theme=\"theme\"\n    :class=\"\n      cn(\n        'relative overflow-hidden rounded-lg border font-mono text-sm shadow-sm',\n        theme === 'dark' ? 'border-zinc-800 bg-zinc-950 text-zinc-200' : 'border-zinc-200 bg-zinc-50 text-zinc-800',\n        props.class,\n      )\n    \"\n  >\n    <!-- Title bar -->\n    <div\n      :class=\"\n        cn(\n          'flex items-center gap-2 border-b px-4 py-2.5',\n          theme === 'dark' ? 'border-zinc-800 bg-zinc-900' : 'border-zinc-200 bg-zinc-100',\n        )\n      \"\n    >\n      <div class=\"flex gap-1.5\">\n        <span class=\"size-3 rounded-full bg-red-500\" />\n        <span class=\"size-3 rounded-full bg-yellow-500\" />\n        <span class=\"size-3 rounded-full bg-green-500\" />\n      </div>\n      <span :class=\"cn('ml-2 text-xs', theme === 'dark' ? 'text-zinc-400' : 'text-zinc-500')\">{{ title }}</span>\n    </div>\n\n    <!-- Body -->\n    <div ref=\"bodyRef\" class=\"overflow-auto p-4 leading-relaxed\" :style=\"{ maxHeight: props.maxHeight }\">\n      <div v-for=\"(line, i) in shownLines\" :key=\"i\" data-slot=\"terminal-line\" class=\"break-words whitespace-pre-wrap\">\n        <slot name=\"line\" :line=\"line\" :index=\"i\">\n          <div\n            v-if=\"line.type === 'command'\"\n            data-slot=\"terminal-command\"\n            class=\"flex flex-wrap items-baseline gap-x-1.5\"\n          >\n            <span :class=\"cn('shrink-0 font-semibold', theme === 'dark' ? 'text-green-400' : 'text-green-600')\">{{\n              line.prompt\n            }}</span>\n            <span>{{ line.command }}</span>\n          </div>\n          <div v-if=\"line.output\" data-slot=\"terminal-output\" class=\"text-zinc-400\">{{ line.output }}</div>\n        </slot>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/terminal/Terminal.vue"
    },
    {
      "path": "packages/registry-vue/components/terminal/index.ts",
      "content": "export { default as Terminal } from './Terminal.vue'\nexport type { TerminalLine } from './Terminal.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/terminal/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Terminal/command-line display with a macOS-style title bar, command history, prompt character, dark/light themes, auto-scroll, optional typing animation, and a line slot for custom formatting.",
  "categories": [
    "display"
  ]
}