{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"input-group","title":"Input Group","description":"Groups an input or textarea with icons, text, buttons, and keyboard hints inside a single field container.","dependencies":["cn","class-variance-authority"],"registryDependencies":["@sortui/button","@sortui/input","@sortui/textarea"],"files":[{"path":"components/ui/input-group.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"cn\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nfunction InputGroup({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"lg\" | \"xl\" }) {\n  return (\n    <div\n      data-slot=\"input-group\"\n      data-size={size}\n      role=\"group\"\n      className={cn(\n        \"group/input-group relative flex h-(--ig-h) w-full min-w-0 items-center rounded-(--ig-radius) bg-field outline-1 -outline-offset-1 outline-field-border transition-[color,box-shadow,outline-color] duration-200 outline-solid [--ig-block:--spacing(1.5)] [--ig-body:--spacing(2.5)] [--ig-h:--spacing(8)] [--ig-radius:var(--radius-lg)] has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot=input-group-control]:focus-visible]:outline-ring in-data-[slot=combobox-content]:has-[[data-slot=input-group-control]:focus-visible]:ring-0 in-data-[slot=combobox-content]:has-[[data-slot=input-group-control]:focus-visible]:outline-field-border has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:outline-destructive has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-wrap has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-wrap has-[>textarea]:h-auto data-[size=lg]:[--ig-block:--spacing(2)] data-[size=lg]:[--ig-body:--spacing(3)] data-[size=lg]:[--ig-h:--spacing(9)] data-[size=xl]:[--ig-block:--spacing(2)] data-[size=xl]:[--ig-body:--spacing(3)] data-[size=xl]:[--ig-h:--spacing(10)] dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 dark:has-[[data-slot][aria-invalid=true]]:outline-destructive/50 has-[>[data-align=inline-end]>svg:only-child]:[&>[data-slot=input-group-control]]:pr-2 has-[>[data-align=inline-start]>svg:only-child]:[&>[data-slot=input-group-control]]:pl-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text items-center justify-center gap-2 text-xs font-medium text-muted-foreground select-none [--ig-item:--spacing(4)] [--ig-pad:calc((var(--ig-h)-var(--ig-item))/2)] group-data-[disabled=true]/input-group:opacity-50 has-[>[data-size=icon-sm]]:[--ig-item:--spacing(7)] has-[>[data-size=icon-xs]]:[--ig-item:--spacing(6)] has-[>[data-size=sm]]:[--ig-item:--spacing(7)] has-[>[data-size=text]]:[--ig-item:--spacing(5)] has-[>[data-size=xs]]:[--ig-item:--spacing(6)] has-[>button.h-10:not([data-size])]:[--ig-item:--spacing(10)] has-[>button.h-7:not([data-size])]:[--ig-item:--spacing(7)] has-[>button.h-8:not([data-size])]:[--ig-item:--spacing(8)] has-[>button.h-9:not([data-size])]:[--ig-item:--spacing(9)] has-[>button.size-10:not([data-size])]:[--ig-item:--spacing(10)] has-[>button.size-7:not([data-size])]:[--ig-item:--spacing(7)] has-[>button.size-8:not([data-size])]:[--ig-item:--spacing(8)] has-[>button.size-9:not([data-size])]:[--ig-item:--spacing(9)] has-[>button:not([data-size])]:[--ig-item:--spacing(6)] has-[>kbd]:[--ig-item:--spacing(5)] [&:not(:has(>:not([data-slot=input-group-text])))]:[--ig-pad:max(calc((var(--ig-h)-var(--ig-item))/2),--spacing(2.5))] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        \"inline-start\":\n          \"order-first pl-(--ig-pad) has-[>svg:only-child]:pl-(--ig-body)\",\n        \"inline-end\":\n          \"order-last pr-(--ig-pad) has-[>svg:only-child]:pr-(--ig-body)\",\n        \"block-start\":\n          \"order-first w-full flex-none justify-start px-(--ig-body) pt-(--ig-block) pb-0\",\n        \"block-end\":\n          \"order-last w-full flex-none justify-start px-(--ig-body) pt-0 pb-(--ig-block)\",\n      },\n      divider: {\n        true: \"border-border\",\n        false: \"\",\n      },\n    },\n    compoundVariants: [\n      {\n        align: \"inline-start\",\n        divider: true,\n        class: \"min-h-(--ig-h) border-r pr-[calc(var(--ig-pad)-1px)]\",\n      },\n      {\n        align: \"inline-end\",\n        divider: true,\n        class: \"min-h-(--ig-h) border-l pl-[calc(var(--ig-pad)-1px)]\",\n      },\n      {\n        align: \"block-start\",\n        divider: true,\n        class: \"border-b pb-[calc(var(--ig-block)-1px)]\",\n      },\n      {\n        align: \"block-end\",\n        divider: true,\n        class: \"border-t pt-[calc(var(--ig-block)-1px)]\",\n      },\n    ],\n    defaultVariants: {\n      align: \"inline-start\",\n      divider: false,\n    },\n  }\n)\n\nfunction InputGroupAddon({\n  className,\n  align = \"inline-start\",\n  divider = false,\n  style,\n  ref: refProp,\n  onClick,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof inputGroupAddonVariants> & {\n    style?: React.CSSProperties & { \"--ig-item\"?: string | number }\n  }) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  const setRef = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      ref.current = node\n      if (typeof refProp === \"function\") return refProp(node)\n      if (refProp) refProp.current = node\n    },\n    [refProp]\n  )\n  const customItem = style?.[\"--ig-item\"]\n\n  React.useEffect(() => {\n    const el = ref.current\n    if (\n      !el ||\n      customItem !== undefined ||\n      align === \"block-start\" ||\n      align === \"block-end\"\n    )\n      return\n    const measure = () => {\n      let max = 0\n      for (const child of el.children) {\n        if (child.matches(\"[data-slot=input-group-text]\")) continue\n        const h =\n          child instanceof HTMLElement\n            ? child.offsetHeight\n            : child.clientHeight || child.getBoundingClientRect().height\n        if (h > max) max = h\n      }\n      if (max > 0) el.style.setProperty(\"--ig-item\", `${max}px`)\n      else el.style.removeProperty(\"--ig-item\")\n    }\n    if (typeof ResizeObserver === \"undefined\") {\n      measure()\n      return\n    }\n    const resize = new ResizeObserver(measure)\n    const observeChildren = () => {\n      resize.disconnect()\n      for (const child of el.children) resize.observe(child)\n      measure()\n    }\n    const mutation = new MutationObserver(observeChildren)\n    mutation.observe(el, { childList: true })\n    observeChildren()\n    return () => {\n      resize.disconnect()\n      mutation.disconnect()\n    }\n  }, [align, customItem])\n\n  return (\n    <div\n      ref={setRef}\n      role=\"group\"\n      data-slot=\"input-group-addon\"\n      data-align={align}\n      data-divider={divider || undefined}\n      style={style}\n      className={cn(inputGroupAddonVariants({ align, divider }), className)}\n      onClick={(e) => {\n        onClick?.(e)\n        if (\n          e.defaultPrevented ||\n          (e.target as Element).closest(\n            \"a, button, input, select, textarea, [role=checkbox], [role=switch]\"\n          )\n        ) {\n          return\n        }\n        const group = e.currentTarget.parentElement\n        if (!group) return\n        const control =\n          group.querySelector<HTMLElement>(\"[data-slot=input-group-control]\") ??\n          group.querySelector<HTMLElement>(\n            \"input:not([type=hidden],[aria-hidden=true]), textarea\"\n          )\n        control?.focus()\n      }}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupButtonVariants = cva(\"flex items-center\", {\n  variants: {\n    size: {\n      xs: \"h-6 gap-1 rounded-md px-2 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n      sm: \"h-7 gap-1.5 rounded-md px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n      \"icon-xs\":\n        \"size-6 rounded-md p-0 has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 has-[>svg]:p-0 [&>svg:not([class*='size-'])]:size-3.5\",\n      \"icon-sm\":\n        \"size-7 rounded-md p-0 has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 has-[>svg]:p-0\",\n      text: \"h-auto gap-1 rounded-xs p-0 text-xs hover:bg-transparent hover:opacity-70 disabled:text-foreground has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 aria-expanded:bg-transparent [&>svg:not([class*='size-'])]:size-3.5\",\n    },\n  },\n  defaultVariants: {\n    size: \"xs\",\n  },\n})\n\nfunction InputGroupButton({\n  className,\n  type = \"button\",\n  variant = \"ghost\",\n  size = \"xs\",\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, \"size\" | \"type\"> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: \"button\" | \"submit\" | \"reset\"\n  }) {\n  return (\n    <Button\n      type={type}\n      data-size={size}\n      variant={variant}\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"input-group-text\"\n      className={cn(\n        \"flex items-center gap-2 text-xs text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<\"input\">) {\n  return (\n    <Input\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 rounded-none bg-transparent px-(--ig-body) shadow-none ring-0 outline-0 group-data-[size=lg]/input-group:h-9 group-data-[size=xl]/input-group:h-10 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<\"textarea\">) {\n  return (\n    <Textarea\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 resize-none rounded-none bg-transparent px-(--ig-body) py-2.5 shadow-none ring-0 outline-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n}\n","type":"registry:ui"}],"cssVars":{"theme":{"color-field":"var(--field)","color-field-border":"var(--field-border)","text-xs--letter-spacing":"-0.005em","text-xs":"0.8125rem","text-xs--line-height":"calc(1.25 / 0.8125)"},"light":{"field":"oklch(0.556 0 0 / 2.5%)","field-border":"oklch(0.145 0 0 / 10%)"},"dark":{"field":"oklch(1 0 0 / 2.5%)","field-border":"oklch(1 0 0 / 15%)"}},"categories":["forms"],"type":"registry:ui"}