{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-group",
  "type": "registry:ui",
  "title": "Input Group",
  "description": "Wraps an input with addons, icons, and prefix/suffix elements into a single bordered unit.",
  "dependencies": [
    "@saasflare/ui",
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "components/ui/input-group.tsx",
      "type": "registry:ui",
      "content": "// @toreview\n\"use client\"\n\n/**\n * @fileoverview InputGroup primitive — composes an input with inline addons, buttons,\n * and prefix/suffix elements into a single visual unit. Supports focus, error, and\n * disabled states with consistent border and ring styling. Part of the Saasflare base component layer.\n * @module packages/ui/components/ui/input-group\n * @layer core\n *\n * @component\n * @example\n * import { InputGroup, InputGroupInput, InputGroupAddon } from '@saasflare/ui';\n * <InputGroup>\n *   <InputGroupAddon>$</InputGroupAddon>\n *   <InputGroupInput placeholder=\"Amount\" />\n * </InputGroup>\n */\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\nimport { Button } from \"@saasflare/ui\"\nimport { Input } from \"@saasflare/ui\"\nimport { Textarea } from \"@saasflare/ui\"\n\n/** Props for {@link InputGroup}. Extends the Saasflare theme contract (surface/radius/animated). */\ninterface InputGroupProps\n  extends Omit<React.ComponentProps<\"div\">, keyof SaasflareComponentProps>,\n    SaasflareComponentProps {}\n\n/**\n * InputGroup — bordered container that visually unifies an input with inline\n * addons, buttons, and prefix/suffix text. Owns the focus ring, error styling,\n * and the theme axes (surface/radius/animated) for the whole group.\n *\n * @component\n * @example\n * <InputGroup>\n *   <InputGroupAddon>$</InputGroupAddon>\n *   <InputGroupInput placeholder=\"Amount\" />\n * </InputGroup>\n */\nfunction InputGroup({ className, surface, radius, animated, iconWeight, ...props }: InputGroupProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  return (\n    <div\n      data-slot=\"input-group\"\n      data-surface={sf.surface}\n      data-radius={sf.radius}\n      data-animated={String(sf.animated)}\n      role=\"group\"\n      className={cn(\n        \"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30\",\n        \"h-9 min-w-0 has-[>textarea]:h-auto\",\n\n        // Variants based on alignment.\n        \"has-[>[data-align=inline-start]]:[&>input]:pl-2\",\n        \"has-[>[data-align=inline-end]]:[&>input]:pr-2\",\n        \"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3\",\n        \"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3\",\n\n        // Focus state.\n        \"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50\",\n\n        // Error state.\n        \"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40\",\n\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        \"inline-start\":\n          \"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]\",\n        \"inline-end\":\n          \"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]\",\n        \"block-start\":\n          \"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3\",\n        \"block-end\":\n          \"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3\",\n      },\n    },\n    defaultVariants: {\n      align: \"inline-start\",\n    },\n  }\n)\n\n/**\n * InputGroupAddon — non-interactive slot for icons, text, buttons, or kbd hints\n * placed inline or block-aligned around the control. Clicking the addon focuses\n * the sibling input (unless the click lands on a nested button), while still\n * running any consumer-supplied `onClick`.\n *\n * @component\n * @example <InputGroupAddon align=\"inline-end\"><SearchIcon /></InputGroupAddon>\n */\nfunction InputGroupAddon({\n  className,\n  align = \"inline-start\",\n  onClick,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"input-group-addon\"\n      data-align={align}\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      onClick={(e) => {\n        onClick?.(e)\n        if ((e.target as HTMLElement).closest(\"button\")) {\n          return\n        }\n        e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\n      }}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupButtonVariants = cva(\n  \"flex items-center gap-2 text-sm shadow-none\",\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: \"h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5\",\n        \"icon-xs\":\n          \"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0\",\n        \"icon-sm\": \"size-8 p-0 has-[>svg]:p-0\",\n      },\n    },\n    defaultVariants: {\n      size: \"xs\",\n    },\n  }\n)\n\n/**\n * InputGroupButton — compact {@link Button} sized to sit flush inside an\n * InputGroup. Defaults to a ghost variant and the `xs` group sizing. Inherits\n * Button's theme axes; the group-local `size` vocabulary drives only the inline\n * geometry via `inputGroupButtonVariants`.\n *\n * @component\n * @example <InputGroupButton size=\"icon-xs\"><XIcon /></InputGroupButton>\n */\nfunction InputGroupButton({\n  className,\n  type = \"button\",\n  variant = \"ghost\",\n  size = \"xs\",\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, \"size\"> &\n  VariantProps<typeof inputGroupButtonVariants>) {\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\n/**\n * InputGroupText — muted inline text/label slot (e.g. units, prefixes) rendered\n * as a `<span>` inside an InputGroup.\n *\n * @component\n * @example <InputGroupText>https://</InputGroupText>\n */\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * InputGroupInput — thin wrapper over {@link Input} stripped of its own border,\n * radius, and ring so it blends seamlessly into the surrounding InputGroup. The\n * theme axes (surface/radius/animated) are inherited from {@link Input}.\n *\n * @component\n * @example <InputGroupInput placeholder=\"Search\" />\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 border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * InputGroupTextarea — thin wrapper over {@link Textarea} stripped of its own\n * border, radius, ring, and resize handle so it blends into the InputGroup. The\n * theme axes (surface/radius/animated) are inherited from {@link Textarea}.\n *\n * @component\n * @example <InputGroupTextarea placeholder=\"Notes\" />\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 border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  InputGroup,\n  type InputGroupProps,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n}\n",
      "target": "components/ui/input-group.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/input-group.json"
  }
}
