{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "label",
  "title": "Label",
  "description": "A label whose colour follows its control's focus and errors, with a small marker that pops in.",
  "dependencies": [
    "@ui-sdk/core@0.1.0-beta.2",
    "@ui-sdk/react@0.1.0-beta.2"
  ],
  "registryDependencies": [
    "@keyframery/motion"
  ],
  "files": [
    {
      "path": "registry/components/keyframery/primitives/label.tsx",
      "content": "\"use client\"\n\n/* Animated drop-in for `@ui-sdk/react/label`: same part, same props. Swap the import and keep your code.\n   This file adds motion only. Colours, radii, shadows and spacing stay yours. */\n\nimport { Label as LabelPart } from \"@ui-sdk/react/label\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport * from \"@ui-sdk/react/label\"\n\n/* UI SDK accepts a class name or a function of state; keep both working. */\nfunction withClass<S>(base: string, className: string | ((state: S) => string | undefined) | undefined) {\n  return typeof className === \"function\" ? (state: S) => cn(base, className(state)) : cn(base, className)\n}\n\nconst INVALID = '[aria-invalid=\"true\"],[data-invalid]'\n\n/* UI SDK's label carries no state of its own, so the label watches the control it names (its\n   `htmlFor` target, or the control it wraps) and mirrors two facts as attributes for CSS:\n   `data-focused` while focus is anywhere inside that control, `data-invalid` while the control, or\n   a part of it, is marked invalid. It changes nothing about naming, focus or presses. */\nfunction useControlState(htmlFor: string | undefined) {\n  const [node, setNode] = React.useState<HTMLLabelElement | null>(null)\n  const [focused, setFocused] = React.useState(false)\n  const [invalid, setInvalid] = React.useState(false)\n\n  React.useEffect(() => {\n    if (!node) return\n    const doc = node.ownerDocument\n    const control = () => (htmlFor ? doc.getElementById(htmlFor) : null) ?? node\n    const readFocus = () => {\n      const active = doc.activeElement\n      setFocused(!!active && active !== doc.body && (control().contains(active) || node.contains(active)))\n    }\n    const readInvalid = () => {\n      const el = control()\n      setInvalid(el.matches(INVALID) || !!el.querySelector(INVALID))\n    }\n    readFocus()\n    readInvalid()\n    doc.addEventListener(\"focusin\", readFocus)\n    doc.addEventListener(\"focusout\", readFocus)\n    const target = htmlFor ? doc.getElementById(htmlFor) : node\n    const observer = new MutationObserver(readInvalid)\n    if (target) observer.observe(target, { attributes: true, subtree: true, attributeFilter: [\"aria-invalid\", \"data-invalid\"] })\n    return () => {\n      doc.removeEventListener(\"focusin\", readFocus)\n      doc.removeEventListener(\"focusout\", readFocus)\n      observer.disconnect()\n    }\n  }, [node, htmlFor])\n\n  return { setNode, focused, invalid }\n}\n\n/* The label's colour shifts on the house ease when its control takes focus or turns invalid. A\n   `LabelMarker` inside it pops (ease-pop) at the same moment. */\nfunction Label({ className, ref, htmlFor, ...props }: React.ComponentProps<typeof LabelPart>) {\n  const { setNode, focused, invalid } = useControlState(htmlFor)\n  const merged = React.useCallback(\n    (el: HTMLLabelElement | null) => {\n      setNode(el)\n      if (typeof ref === \"function\") ref(el)\n      else if (ref) ref.current = el\n    },\n    [ref, setNode],\n  )\n  return (\n    <LabelPart\n      ref={merged}\n      htmlFor={htmlFor}\n      data-focused={focused || undefined}\n      data-invalid={invalid || undefined}\n      className={withClass(\n        \"group/kf-label transition-[color] duration-[calc(180ms*var(--kf-speed,1))] ease-house motion-reduce:transition-none!\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* Extra: a required or optional marker. It rests small and quiet, and pops up to full size when\n   the control is focused or invalid, and lifts a pixel while invalid. */\nfunction LabelMarker({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"label-marker\"\n      className={cn(\n        \"inline-block origin-left scale-[0.82] opacity-60\",\n        \"transition-[scale,opacity,translate] duration-[calc(420ms*var(--kf-speed,1))] ease-pop\",\n        \"group-data-focused/kf-label:scale-100 group-data-focused/kf-label:opacity-100\",\n        \"group-data-invalid/kf-label:scale-100 group-data-invalid/kf-label:opacity-100 group-data-invalid/kf-label:-translate-y-px\",\n        \"motion-reduce:transition-none!\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label, LabelMarker }\n",
      "type": "registry:component",
      "target": "components/keyframery/primitives/label.tsx"
    },
    {
      "path": "registry/components/keyframery/label.tsx",
      "content": "\"use client\"\n\n/* Styled label: the animated primitive plus the Keyframery look. */\n\nimport * as React from \"react\"\n\nimport { Label as LabelPrimitive, LabelMarker as LabelMarkerPrimitive } from \"@/components/keyframery/primitives/label\"\nimport { cn } from \"@/lib/utils\"\n\n/* Quiet at rest, full ink while its control has focus, destructive while the control is invalid. */\nfunction Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive>) {\n  return (\n    <LabelPrimitive\n      className={cn(\n        \"inline-flex items-center gap-1.5 text-[13px] leading-none font-medium text-muted-foreground select-none\",\n        \"data-focused:text-foreground data-invalid:text-destructive\",\n        \"has-[[data-disabled]]:opacity-50\",\n        className as string,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* A small pill for \"Required\" or \"Optional\" that pops when the control is focused or invalid. */\nfunction LabelMarker({ className, ...props }: React.ComponentProps<typeof LabelMarkerPrimitive>) {\n  return (\n    <LabelMarkerPrimitive\n      className={cn(\n        \"rounded-full bg-muted px-1.5 py-[3px] text-[10.5px] leading-none font-medium text-muted-foreground\",\n        /* The pop stays on scale, opacity and lift; the colours shift on the house ease. */\n        \"[transition:scale_calc(420ms*var(--kf-speed,1))_var(--ease-pop),opacity_calc(420ms*var(--kf-speed,1))_var(--ease-pop),translate_calc(420ms*var(--kf-speed,1))_var(--ease-pop),color_calc(180ms*var(--kf-speed,1))_var(--ease-house),background-color_calc(180ms*var(--kf-speed,1))_var(--ease-house)]\",\n        \"motion-reduce:[transition:none]!\",\n        \"group-data-focused/kf-label:bg-foreground/[0.07] group-data-focused/kf-label:text-foreground\",\n        \"group-data-invalid/kf-label:bg-destructive/10 group-data-invalid/kf-label:text-destructive\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label, LabelMarker }\n",
      "type": "registry:component",
      "target": "components/keyframery/label.tsx"
    }
  ],
  "type": "registry:component"
}