{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "pin-input",
  "title": "Pin Input",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/pin-input/PinInput.vue",
      "content": "<script setup lang=\"ts\" generic=\"Type extends 'text' | 'number' = 'text'\">\nimport { computed, provide, toRef } from 'vue'\nimport type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'\nimport type { HTMLAttributes } from 'vue'\nimport { reactiveOmit } from '@vueuse/core'\nimport { PinInputRoot, useForwardPropsEmits } from 'reka-ui'\nimport { cn } from '@/lib/utils'\n\ntype PinInputStatus = 'error' | 'warning' | 'success' | 'default'\ntype PinInputSize = 'sm' | 'md' | 'lg'\n\nconst props = withDefaults(\n  defineProps<\n    PinInputRootProps<Type> & {\n      class?: HTMLAttributes['class']\n      mask?: boolean\n      autoSubmit?: boolean\n      status?: PinInputStatus\n      size?: PinInputSize\n    }\n  >(),\n  {\n    otp: true,\n    mask: false,\n    autoSubmit: false,\n    status: 'default',\n    size: 'md',\n  },\n)\n\nconst emits = defineEmits<\n  Omit<PinInputRootEmits<Type>, 'complete'> & {\n    complete: [value: string]\n  }\n>()\n\nconst delegatedProps = reactiveOmit(props, 'class', 'mask', 'autoSubmit', 'status', 'size')\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nprovide('pinInputContext', {\n  mask: toRef(props, 'mask'),\n  status: toRef(props, 'status'),\n  size: toRef(props, 'size'),\n})\n\nfunction handleComplete(value: string[]) {\n  const joined = value.join('')\n  emits('complete', joined)\n  if (props.autoSubmit) {\n    const event = new CustomEvent('pin-submit', { detail: joined, bubbles: true })\n    document.dispatchEvent(event)\n  }\n}\n</script>\n\n<template>\n  <PinInputRoot\n    :otp=\"props.otp\"\n    data-uipkge\n    data-slot=\"pin-input\"\n    v-bind=\"forwarded\"\n    :class=\"cn('flex items-center gap-2 disabled:cursor-not-allowed has-disabled:opacity-50', props.class)\"\n    @complete=\"handleComplete\"\n  >\n    <slot />\n  </PinInputRoot>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/pin-input/PinInput.vue"
    },
    {
      "path": "packages/registry-vue/components/pin-input/PinInputGroup.vue",
      "content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport type { HTMLAttributes } from 'vue'\nimport { reactiveOmit } from '@vueuse/core'\nimport { Primitive, useForwardProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()\nconst delegatedProps = reactiveOmit(props, 'class')\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n  <Primitive\n    data-uipkge\n    data-slot=\"pin-input-group\"\n    v-bind=\"forwardedProps\"\n    :class=\"cn('flex items-center', props.class)\"\n  >\n    <slot />\n  </Primitive>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/pin-input/PinInputGroup.vue"
    },
    {
      "path": "packages/registry-vue/components/pin-input/PinInputSeparator.vue",
      "content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport { Minus } from 'lucide-vue-next'\nimport { Primitive, useForwardProps } from 'reka-ui'\n\nconst props = defineProps<PrimitiveProps>()\nconst forwardedProps = useForwardProps(props)\n</script>\n\n<template>\n  <Primitive data-uipkge data-slot=\"pin-input-separator\" v-bind=\"forwardedProps\">\n    <slot>\n      <Minus />\n    </slot>\n  </Primitive>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/pin-input/PinInputSeparator.vue"
    },
    {
      "path": "packages/registry-vue/components/pin-input/PinInputSlot.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\nimport type { PinInputInputProps } from 'reka-ui'\nimport type { HTMLAttributes, Ref } from 'vue'\nimport { reactiveOmit } from '@vueuse/core'\nimport { PinInputInput, useForwardProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\n\ntype PinInputStatus = 'error' | 'warning' | 'success' | 'default'\ntype PinInputSize = 'sm' | 'md' | 'lg'\n\ninterface PinInputContext {\n  mask: Ref<boolean>\n  status: Ref<PinInputStatus>\n  size: Ref<PinInputSize>\n}\n\nconst props = defineProps<\n  PinInputInputProps & {\n    class?: HTMLAttributes['class']\n    /** Override the inherited mask flag for this slot. */\n    mask?: boolean\n  }\n>()\n\nconst ctx = inject<PinInputContext | null>('pinInputContext', null)\nconst forwarded = useForwardProps(reactiveOmit(props, 'class', 'mask'))\n\nconst effectiveMask = computed(() => props.mask ?? ctx?.mask.value ?? false)\nconst status = computed(() => ctx?.status.value ?? 'default')\nconst size = computed(() => ctx?.size.value ?? 'md')\n\nconst inputType = computed(() => (effectiveMask.value ? 'password' : 'text'))\n\nconst sizeClasses = computed(() => {\n  switch (size.value) {\n    case 'sm':\n      return 'h-8 w-8 text-sm'\n    case 'lg':\n      return 'h-12 w-12 text-xl'\n    default:\n      return 'h-10 w-10 text-base'\n  }\n})\n\nconst statusClasses = computed(() => {\n  switch (status.value) {\n    case 'error':\n      return 'border-destructive focus:border-destructive focus:ring-destructive/40 text-destructive'\n    case 'warning':\n      return 'border-warning focus:border-warning focus:ring-warning/40 text-warning'\n    case 'success':\n      return 'border-success focus:border-success focus:ring-success/40 text-success'\n    default:\n      return ''\n  }\n})\n</script>\n\n<template>\n  <PinInputInput\n    :type=\"inputType\"\n    data-uipkge\n    data-slot=\"pin-input-slot\"\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'border-input bg-background text-foreground relative -ml-px flex items-center justify-center border text-center shadow-xs transition-[border-color,box-shadow] outline-none first:ml-0 first:rounded-l-md last:rounded-r-md',\n        'focus:border-ring focus:ring-ring/40 focus:relative focus:z-10 focus:ring-2',\n        'disabled:cursor-not-allowed disabled:opacity-50',\n        sizeClasses,\n        statusClasses,\n        props.class,\n      )\n    \"\n  />\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/pin-input/PinInputSlot.vue"
    },
    {
      "path": "packages/registry-vue/components/pin-input/index.ts",
      "content": "export { default as PinInput } from './PinInput.vue'\nexport { default as PinInputGroup } from './PinInputGroup.vue'\nexport { default as PinInputSeparator } from './PinInputSeparator.vue'\nexport { default as PinInputSlot } from './PinInputSlot.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/pin-input/index.ts"
    }
  ],
  "dependencies": [
    "@vueuse/core",
    "lucide-vue-next",
    "reka-ui"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "One-time-code input — N separate boxes that auto-advance and accept paste. Use for SMS verification, 2FA, and short numeric codes. Length, masking, and per-slot status all configurable.",
  "categories": [
    "form"
  ]
}