{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "float-label",
  "title": "Float Label",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/float-label/FloatLabel.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, onMounted, ref } from 'vue'\nimport { cn } from '@/lib/utils'\n\ninterface Props {\n  label: string\n  required?: boolean\n  disabled?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\nconst isFocused = ref(false)\nconst hasValue = ref(false)\nconst wrapperRef = ref<HTMLElement | null>(null)\n\nconst isFloating = computed(() => isFocused.value || hasValue.value)\n\nfunction checkValue(target: EventTarget | null) {\n  const el = target as HTMLElement | null\n  if (!el) return\n  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n    hasValue.value = !!el.value\n  } else if (el instanceof HTMLSelectElement) {\n    hasValue.value = !!el.value\n  } else {\n    const input = el.querySelector?.('input, textarea, select') as\n      | HTMLInputElement\n      | HTMLTextAreaElement\n      | HTMLSelectElement\n      | null\n    if (input) hasValue.value = !!input.value\n  }\n}\n\nfunction handleFocusin(event: FocusEvent) {\n  isFocused.value = true\n  checkValue(event.target)\n}\n\nfunction handleFocusout(event: FocusEvent) {\n  isFocused.value = false\n  checkValue(event.target)\n}\n\nfunction handleInput(event: Event) {\n  checkValue(event.target)\n}\n\n// Check for prefilled value on mount (before any focus/input events fire)\nonMounted(() => {\n  if (!wrapperRef.value) return\n  const input = wrapperRef.value.querySelector('input, textarea, select') as\n    | HTMLInputElement\n    | HTMLTextAreaElement\n    | HTMLSelectElement\n    | null\n  if (input) hasValue.value = !!input.value\n})\n\nconst wrapperClasses = computed(() =>\n  cn('relative flex flex-col', props.disabled && 'opacity-50 cursor-not-allowed', props.class),\n)\n\nconst labelClasses = computed(() =>\n  cn(\n    'text-muted-foreground pointer-events-none absolute left-3 z-10 bg-transparent px-1 text-sm transition-all duration-200',\n    !isFloating.value && 'top-1/2 -translate-y-1/2',\n    isFloating.value && 'top-0 -translate-y-1/2 scale-75 bg-background text-foreground',\n    isFocused.value && 'text-ring',\n    props.required && \"after:text-destructive after:ml-0.5 after:content-['*']\",\n  ),\n)\n</script>\n\n<template>\n  <div\n    ref=\"wrapperRef\"\n    :class=\"wrapperClasses\"\n    data-uipkge\n    data-slot=\"float-label\"\n    :data-floating=\"isFloating\"\n    @focusin=\"handleFocusin\"\n    @focusout=\"handleFocusout\"\n    @input=\"handleInput\"\n    @change=\"handleInput\"\n  >\n    <label :class=\"labelClasses\">\n      {{ label }}\n    </label>\n    <slot />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/float-label/FloatLabel.vue"
    },
    {
      "path": "packages/registry-vue/components/float-label/index.ts",
      "content": "export { default as FloatLabel } from './FloatLabel.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/float-label/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Floating label wrapper for any input element. The label floats up when the input is focused or has a value. Wrap it around any input, select, or textarea. Supports required indicator and disabled state.",
  "categories": [
    "control",
    "form"
  ]
}