{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "password-input",
  "title": "Password Input",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/password-input/PasswordInput.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport { Eye, EyeOff } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { passwordInputVariants } from './password-input.variants'\n\ninterface Props {\n  modelValue?: string\n  defaultValue?: string\n  placeholder?: string\n  size?: 'sm' | 'default' | 'lg'\n  variant?: 'outlined' | 'filled' | 'borderless'\n  disabled?: boolean\n  readonly?: boolean\n  showStrength?: boolean\n  showToggle?: boolean\n  minLength?: number\n  maxlength?: number\n  id?: string\n  name?: string\n  autocomplete?: string\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  placeholder: 'Enter password',\n  size: 'default',\n  variant: 'outlined',\n  disabled: false,\n  readonly: false,\n  showStrength: false,\n  showToggle: true,\n  minLength: 0,\n  autocomplete: 'current-password',\n})\n\nconst emits = defineEmits<{\n  'update:modelValue': [value: string]\n  focus: [event: FocusEvent]\n  blur: [event: FocusEvent]\n}>()\n\nconst passwordVisible = ref(false)\n\nconst computedType = computed(() => (passwordVisible.value ? 'text' : 'password'))\n\nconst inputValue = computed({\n  get: () => props.modelValue ?? '',\n  set: (val: string) => emits('update:modelValue', val),\n})\n\ninterface StrengthResult {\n  score: number\n  label: 'weak' | 'fair' | 'good' | 'strong'\n  color: string\n  barColor: string\n  percent: number\n}\n\nconst strength = computed<StrengthResult>(() => {\n  const pwd = inputValue.value\n  if (!pwd) return { score: 0, label: 'weak', color: '', barColor: 'bg-transparent', percent: 0 }\n\n  let score = 0\n  if (pwd.length >= 6) score++\n  if (pwd.length >= 10) score++\n  if (/[A-Z]/.test(pwd) && /[a-z]/.test(pwd)) score++\n  if (/\\d/.test(pwd)) score++\n  if (/[^A-Za-z0-9]/.test(pwd)) score++\n\n  if (score <= 1) {\n    return { score, label: 'weak', color: 'text-destructive', barColor: 'bg-destructive', percent: 25 }\n  }\n  if (score <= 2) {\n    return {\n      score,\n      label: 'fair',\n      color: 'text-[var(--warning)]',\n      barColor: 'bg-[var(--warning)]',\n      percent: 50,\n    }\n  }\n  if (score <= 3) {\n    return {\n      score,\n      label: 'good',\n      color: 'text-[var(--info)]',\n      barColor: 'bg-[var(--info)]',\n      percent: 75,\n    }\n  }\n  return {\n    score,\n    label: 'strong',\n    color: 'text-[var(--success)]',\n    barColor: 'bg-[var(--success)]',\n    percent: 100,\n  }\n})\n\nconst meetsMinLength = computed(() => inputValue.value.length >= props.minLength)\n\nfunction toggleVisibility() {\n  if (props.disabled || props.readonly) return\n  passwordVisible.value = !passwordVisible.value\n}\n\nconst wrapperClasses = computed(() =>\n  cn(\n    passwordInputVariants({ size: props.size, variant: props.variant }),\n    'focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]',\n    props.disabled && 'pointer-events-none opacity-50 cursor-not-allowed bg-muted/30',\n    props.class,\n  ),\n)\n\nconst inputPadding = computed(() => {\n  if (props.size === 'sm') return 'px-2.5'\n  if (props.size === 'lg') return 'px-4'\n  return 'px-3'\n})\n\nconst togglePadding = computed(() => {\n  if (props.size === 'sm') return 'pr-2'\n  if (props.size === 'lg') return 'pr-3'\n  return 'pr-2.5'\n})\n</script>\n\n<template>\n  <div class=\"flex w-full flex-col gap-2\">\n    <div :class=\"wrapperClasses\" data-uipkge data-slot=\"password-input\" :data-size=\"size\" :data-variant=\"variant\">\n      <input\n        :id=\"id\"\n        v-model=\"inputValue\"\n        :type=\"computedType\"\n        :disabled=\"disabled\"\n        :readonly=\"readonly\"\n        :maxlength=\"maxlength\"\n        :placeholder=\"placeholder\"\n        :name=\"name\"\n        :autocomplete=\"autocomplete\"\n        :class=\"cn('placeholder:text-muted-foreground w-full min-w-0 flex-1 bg-transparent outline-none', inputPadding)\"\n        @focus=\"emits('focus', $event)\"\n        @blur=\"emits('blur', $event)\"\n      />\n      <div v-if=\"showToggle\" class=\"flex shrink-0 items-center\" :class=\"togglePadding\">\n        <button\n          type=\"button\"\n          :aria-label=\"passwordVisible ? 'Hide password' : 'Show password'\"\n          :aria-pressed=\"passwordVisible\"\n          :disabled=\"disabled || readonly\"\n          class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 shrink-0 rounded p-0.5 transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed\"\n          @mousedown.prevent\n          @click=\"toggleVisibility\"\n        >\n          <Eye v-if=\"passwordVisible\" class=\"size-4\" aria-hidden=\"true\" />\n          <EyeOff v-else class=\"size-4\" aria-hidden=\"true\" />\n        </button>\n      </div>\n    </div>\n\n    <div v-if=\"showStrength && inputValue\" class=\"flex flex-col gap-1.5\">\n      <div class=\"bg-muted h-1.5 w-full overflow-hidden rounded-full\">\n        <div\n          class=\"h-full rounded-full transition-all duration-300\"\n          :class=\"strength.barColor\"\n          :style=\"{ width: `${strength.percent}%` }\"\n        />\n      </div>\n      <div class=\"flex items-center justify-between text-xs\">\n        <span :class=\"strength.color\" class=\"font-medium capitalize\">{{ strength.label }}</span>\n        <span v-if=\"minLength > 0\" :class=\"meetsMinLength ? 'text-[var(--success)]' : 'text-muted-foreground'\">\n          {{ inputValue.length }} / {{ minLength }} chars\n        </span>\n      </div>\n    </div>\n\n    <p v-if=\"minLength > 0 && !showStrength && inputValue\" class=\"text-muted-foreground text-xs\">\n      Minimum {{ minLength }} characters\n    </p>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/password-input/PasswordInput.vue"
    },
    {
      "path": "packages/registry-vue/components/password-input/password-input.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\nexport const passwordInputVariants = cva(\n  'flex w-full items-center gap-1.5 overflow-hidden border transition-[color,box-shadow] outline-none rounded-md',\n  {\n    variants: {\n      size: {\n        sm: 'h-8 text-xs',\n        default: 'h-9 text-base md:text-sm',\n        lg: 'h-11 text-base',\n      },\n      variant: {\n        outlined: 'border-input bg-transparent shadow-xs',\n        filled: 'border-transparent bg-muted/50 shadow-none',\n        borderless: 'border-transparent bg-transparent shadow-none',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      variant: 'outlined',\n    },\n  },\n)\n\nexport type PasswordInputVariants = VariantProps<typeof passwordInputVariants>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/password-input/password-input.variants.ts"
    },
    {
      "path": "packages/registry-vue/components/password-input/index.ts",
      "content": "export { default as PasswordInput } from './PasswordInput.vue'\nexport { passwordInputVariants } from './password-input.variants'\nexport type { PasswordInputVariants } from './password-input.variants'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/password-input/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Password input with show/hide toggle (Eye/EyeOff) and optional strength meter (weak/fair/good/strong with colored bar). Supports min length display, disabled, placeholder, and size variants.",
  "categories": [
    "control",
    "form"
  ]
}