{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "label-float",
  "title": "Float label",
  "description": "Styled label: the Float set. 5 decorative label variations (FloatLabel, ShrinkLabel, InlineFloatLabel, OverlapLabel, SlideLabel) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/label-float.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Float label family: 5 labels that wrap a REAL input and move the label according\n   to focus/value state. The label rests in the empty field and rises when the input\n   is focused or a value is typed. The label is a real <label> bound to the input\n   with htmlFor (the id comes from React.useId()). Because the label sits over the\n   box it carries pointer-events-none; a click passes through to the input and focus\n   still happens.\n   The box height comes from its own float scale (a notch taller than the base\n   Button scale so the label has room to rise). The value and focus state live in\n   the root component, never inside a subtree that opens and closes. Color comes\n   ONLY from tokens, via alpha color-mix. The motion is disabled through\n   useReducedMotion(); spring transitions are written as type: \"spring\" as const. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\n/* Box scale for the variants where the label sits outside. */\nconst outerBox: Record<StyledSize, string> = {\n  sm: \"h-9\",\n  md: \"h-10\",\n  lg: \"h-11\",\n  xl: \"h-12\",\n}\n\n/* A taller box for the variants where the label stays inside. */\nconst innerBox: Record<StyledSize, string> = {\n  sm: \"h-11\",\n  md: \"h-12\",\n  lg: \"h-14\",\n  xl: \"h-16\",\n}\n\nconst text: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n}\n\nconst inputText: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n}\n\n/* y offset for a label that rises above the box (from the centre). */\nconst aboveY: Record<StyledSize, number> = { sm: -26, md: -28, lg: -30, xl: -34 }\n\n/* y offset for a label that sits on the border (half the box height). */\nconst borderY: Record<StyledSize, number> = { sm: -18, md: -20, lg: -22, xl: -24 }\n\n/* y offset for a label that moves towards the top edge inside the box. */\nconst insideY: Record<StyledSize, number> = { sm: -10, md: -12, lg: -14, xl: -17 }\n\nconst inputBase =\n  \"w-56 rounded-md border border-field-border bg-transparent px-3 text-foreground outline-none transition-colors placeholder:text-transparent focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst labelBase =\n  \"pointer-events-none absolute inset-y-0 left-3 inline-flex w-fit items-center font-medium leading-none select-none\"\n\nconst spring = { type: \"spring\" as const, stiffness: 420, damping: 32 }\n\nexport interface FloatProps {\n  className?: string\n  size?: StyledSize\n  children?: React.ReactNode\n  placeholder?: string\n}\n\n/* Focus and value state. active: the state in which the label should rise. */\nfunction useFloatField() {\n  const id = React.useId()\n  const [value, setValue] = React.useState(\"\")\n  const [focused, setFocused] = React.useState(false)\n  return {\n    id,\n    value,\n    setValue,\n    focused,\n    setFocused,\n    active: focused || value.length > 0,\n  }\n}\n\n/* Shared shell: the input plus an animated label over it. activeStyle carries each variant's motion signature. */\nfunction FloatShell({\n  className,\n  size = \"md\",\n  children,\n  fallback,\n  boxClass,\n  inputClass,\n  labelClass,\n  activeStyle,\n  restStyle,\n}: FloatProps & {\n  fallback: string\n  boxClass: string\n  inputClass?: string\n  labelClass?: string\n  activeStyle: { y: number; scale: number; x?: number }\n  restStyle?: { y: number; scale: number; x?: number }\n}) {\n  const { id, value, setValue, focused, setFocused, active } = useFloatField()\n  const reduce = useReducedMotion()\n  const rest = restStyle ?? { y: 0, scale: 1, x: 0 }\n\n  return (\n    <span data-slot=\"styled-label\" className={cn(\"inline-flex flex-col pt-3\", className)}>\n      <span className=\"relative inline-flex w-fit\">\n        <input\n          id={id}\n          type=\"text\"\n          value={value}\n          placeholder={fallback}\n          onChange={(e) => setValue(e.target.value)}\n          onFocus={() => setFocused(true)}\n          onBlur={() => setFocused(false)}\n          className={cn(inputBase, inputText[size], boxClass, inputClass)}\n        />\n        <motion.label\n          htmlFor={id}\n          style={{ transformOrigin: \"left center\" }}\n          className={cn(\n            labelBase,\n            text[size],\n            active ? \"text-info\" : \"text-muted-foreground\",\n            labelClass\n          )}\n          animate={active ? activeStyle : rest}\n          transition={reduce ? { duration: 0 } : spring}\n        >\n          {children ?? fallback}\n        </motion.label>\n      </span>\n    </span>\n  )\n}\n\n/* Float: the label rises above the box, the classic float behavior. */\nexport function FloatLabel({ placeholder = \"Email\", ...props }: FloatProps) {\n  return (\n    <FloatShell\n      fallback={placeholder}\n      boxClass={outerBox[props.size ?? \"md\"]}\n      activeStyle={{ y: aboveY[props.size ?? \"md\"], scale: 0.85 }}\n      {...props}\n    />\n  )\n}\n\n/* Shrink: the label stays INSIDE the box, moves towards the top edge and shrinks. */\nexport function ShrinkLabel({ placeholder = \"Email\", ...props }: FloatProps) {\n  const size = props.size ?? \"md\"\n  return (\n    <FloatShell\n      fallback={placeholder}\n      boxClass={innerBox[size]}\n      inputClass=\"pt-4\"\n      activeStyle={{ y: insideY[size], scale: 0.75 }}\n      {...props}\n    />\n  )\n}\n\n/* InlineFloat: the label stays inside the box but moves to the line above like an inline heading, without shrinking fully. */\nexport function InlineFloatLabel({ placeholder = \"Email\", ...props }: FloatProps) {\n  const size = props.size ?? \"md\"\n  return (\n    <FloatShell\n      fallback={placeholder}\n      boxClass={innerBox[size]}\n      inputClass=\"pt-4\"\n      labelClass=\"uppercase tracking-[0.12em]\"\n      activeStyle={{ y: insideY[size], scale: 0.85 }}\n      {...props}\n    />\n  )\n}\n\n/* Overlap: the label rides on the top border; it carries a ground chip behind it so it looks like it cuts the border. */\nexport function OverlapLabel({ placeholder = \"Email\", ...props }: FloatProps) {\n  const size = props.size ?? \"md\"\n  return (\n    <FloatShell\n      fallback={placeholder}\n      boxClass={outerBox[size]}\n      labelClass=\"rounded bg-background px-1\"\n      activeStyle={{ y: borderY[size], scale: 0.85 }}\n      {...props}\n    />\n  )\n}\n\n/* Slide: etiket yukari cikarken sola da kayar, kutunun disinda hizalanir. */\nexport function SlideLabel({ placeholder = \"Email\", ...props }: FloatProps) {\n  const size = props.size ?? \"md\"\n  return (\n    <FloatShell\n      fallback={placeholder}\n      boxClass={outerBox[size]}\n      activeStyle={{ y: aboveY[size], scale: 0.9, x: -12 }}\n      restStyle={{ y: 0, scale: 1, x: 0 }}\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/label-float.tsx"
    }
  ],
  "categories": [
    "styled",
    "label"
  ],
  "type": "registry:component"
}