{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-group",
  "title": "InputGroup",
  "description": "shadcn/ui(new-york-v4) input-group의 유니버설(웹+네이티브 한 벌 소스) 포팅. 수정 금지)가 내부에 조립되는 래퍼 구조를 그대로 유지한다. 포팅 판단(원본 대비 차이):",
  "dependencies": [
    "@unikit-bridge/ui",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/input-group.tsx",
      "content": "// InputGroup — shadcn/ui（new-york-v4） input-group.tsx 유니버설 포팅 （사이클 40, T-2a）.\n// 원본: shadcn-ref/ui/input-group.tsx — InputButtonTextarea（기존 컴포넌트,\n// 수정 금지）가 내부에 조립되는 래퍼 구조를 그대로 유지한다.\n//\n// 포팅 판단（원본 대비 차이）:\n// - 원본의 has-*  group-data-*  ［&>］ 셀렉터 스타일은 네이티브에 data 속성\n//   DOM 셀렉터가 없어 context + 조건부 클래스로 근사한다. 원본 셀렉터\n//   （has-［>［data-align=］］has-［>textarea］）가 직계 자식만 보므로, 루트가\n//   children을 1단 순회해 정렬textareaaria-invalid를 정적으로 수집해\n//   context로 내려준다 — 첫 렌더（SSR 포함）부터 반영되고 매 렌더 재수집된다.\n//   임의 래퍼로 감싼 자식은 원본 셀렉터와 동일하게 감지되지 않는다（한계）.\n// - 포커스 링: 원본은 컨트롤 포커스 시 그룹에 링을 옮기지만（셀렉터） 브리지\n//   TextInputProps에 onFocus/onBlur 계약이 없어 그룹이 포커스를 알 수 없다.\n//   대신 컨트롤 자체의 슬림 링（ring-1오프셋 0）으로 근사한다（한계로드맵）.\n// - Addon 눌림 → input 포커스 이동（원본 DOM querySelector）은 이식 불가 —\n//   View에는 onPress 계약이 없고 Pressable（button）로 바꾸면 내부\n//   InputGroupButton과 웹에서 버튼 중첩이 된다. 폐기（한계）.\n// - 원본 role=\"group\"은 브리지 BridgeRole에 \"group\"을 추가（완료 보고 제기\n//   반영）해 지정한다 — 네이티브는 RN이 모르는 role을 무시한다（한계）.\n// - 임의값 근사: addon의 ml/mr-［-0.45rem］［-0.35rem］은 버튼/kbd 포함 감지\n//   （has-［>button］）와 함께 재현 불가해 제거 — 버튼이 애던 패딩에 붙는다.\n//   rounded-［calc（var（--radius）-5px）］ → rounded-sm（표준 계단）. block-start/end의\n//   group-has-［>input］ 패딩 미세조정（pt-2.5/pb-2.5）은 pt-3/pb-3으로 통일.\n//   ［&>svg］［&>kbd］ 셀렉터 계열은 svg 미지원 포팅이라 제거.\n// - InputGroupButton의 type=\"button\"은 브리지 Pressable이 웹에서 항상\n//   type=\"button\"으로 렌더해 생략（동등）. data-size 속성은 cva size로 전개.\n// - dark: 제거, 웹 전용 토큰（cursorselect-noneshadowtransition）은 web: 접두.\nimport {\n  Children,\n  createContext,\n  forwardRef,\n  isValidElement,\n  useContext,\n  type ReactNode,\n} from \"react\";\nimport {\n  Text,\n  View,\n  type BridgePressableRef,\n  type BridgeTextInputRef,\n  type BridgeViewRef,\n  type ViewProps,\n} from \"@unikit-bridge/ui/primitives\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { Button, type ButtonProps } from \"./button\";\nimport { Input, type InputProps } from \"./input\";\nimport { Textarea, type TextareaProps } from \"./textarea\";\nimport { cn } from \"@/lib/utils\";\n\n// ---------------------------------------------------------------------------\n// 그룹 파생 상태 — 직계 자식 정적 수집（머리 주석）\n// ---------------------------------------------------------------------------\n\ntype InputGroupAlign = \"inline-start\" | \"inline-end\" | \"block-start\" | \"block-end\";\n\ntype InputGroupAligns = {\n  inlineStart: boolean;\n  inlineEnd: boolean;\n  blockStart: boolean;\n  blockEnd: boolean;\n};\n\n/** align 문자열 → 파생 상태 키（케밥→캐멀） */\nconst ALIGN_STATE_KEYS: Record<InputGroupAlign, keyof InputGroupAligns> = {\n  \"inline-start\": \"inlineStart\",\n  \"inline-end\": \"inlineEnd\",\n  \"block-start\": \"blockStart\",\n  \"block-end\": \"blockEnd\",\n};\n\ntype InputGroupDerived = InputGroupAligns & {\n  hasTextarea: boolean;\n  hasInvalid: boolean;\n};\n\n/**\n * 직계 자식에서 그룹 상태를 정적으로 수집한다 — 원본 has-［>］ 직계 셀렉터와\n * 동일 범위. align（InputGroupAddon）textareaaria-invalid（Input/Textarea）.\n */\nfunction collectGroupChildren(children: ReactNode): InputGroupDerived {\n  const derived: InputGroupDerived = {\n    inlineStart: false,\n    inlineEnd: false,\n    blockStart: false,\n    blockEnd: false,\n    hasTextarea: false,\n    hasInvalid: false,\n  };\n  Children.toArray(children).forEach((child) => {\n    if (!isValidElement(child)) return;\n    const props = child.props as {\n      align?: InputGroupAlign | null;\n      \"aria-invalid\"?: boolean;\n    };\n    if (child.type === InputGroupAddon) {\n      const align = props.align ?? \"inline-start\";\n      derived[ALIGN_STATE_KEYS[align]] = true;\n    } else if (child.type === InputGroupTextarea) {\n      derived.hasTextarea = true;\n      if (props[\"aria-invalid\"] === true) derived.hasInvalid = true;\n    } else if (child.type === InputGroupInput) {\n      if (props[\"aria-invalid\"] === true) derived.hasInvalid = true;\n    }\n  });\n  return derived;\n}\n\ntype InputGroupContextValue = InputGroupDerived;\n\nconst InputGroupContext = createContext<InputGroupContextValue | null>(null);\n\n/**\n * 자식 중 문자열/숫자를 내부 Text로 감싼다 — 네이티브는 Text 없이 문자열을\n * 렌더할 수 없고 색 상속도 되지 않는다（이중 지정）. 요소 자식은 그대로 둔다.\n */\nfunction wrapTextChildren(children: ReactNode, textClassName: string) {\n  return Children.map(children, (child) =>\n    typeof child === \"string\" || typeof child === \"number\" ? (\n      <Text className={textClassName}>{child}</Text>\n    ) : (\n      child\n    ),\n  );\n}\n\n// ---------------------------------------------------------------------------\n// 컴포넌트\n// ---------------------------------------------------------------------------\n\nexport type InputGroupProps = ViewProps;\n\nconst InputGroup = forwardRef<BridgeViewRef, InputGroupProps>(\n  ({ className, children, ...props }, ref) => {\n    // 매 렌더 재수집 — 조건부 렌더링제어 prop（aria-invalid） 변화를 즉시 반영.\n    const derived = collectGroupChildren(children);\n    const column = derived.blockStart || derived.blockEnd;\n    return (\n      <InputGroupContext.Provider value={derived}>\n        <View\n          ref={ref}\n          accessibilityRole=\"group\"\n          className={cn(\n            \"relative w-full min-w-0 rounded-md border border-input web:transition-all web:outline-none\",\n            column ? \"flex-col\" : \"flex-row items-center\",\n            // 원본 h-9 → has-［>textarea］/block 정렬 시 h-auto. min-h-9로 바닥 유지.\n            column || derived.hasTextarea ? \"min-h-9\" : \"h-9\",\n            // 원본 aria-invalid: border-destructive + ring-destructive/20.\n            derived.hasInvalid && \"border-destructive web:ring-destructive/20\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </View>\n      </InputGroupContext.Provider>\n    );\n  },\n);\nInputGroup.displayName = \"InputGroup\";\n\nconst inputGroupAddonVariants = cva(\n  \"flex flex-row h-auto items-center justify-center gap-2 py-1.5 web:cursor-text web:select-none\",\n  {\n    variants: {\n      align: {\n        \"inline-start\": \"order-first pl-3\",\n        \"inline-end\": \"order-last pr-3\",\n        \"block-start\": \"order-first w-full justify-start px-3 pt-3\",\n        \"block-end\": \"order-last w-full justify-start px-3 pb-3\",\n      },\n    },\n    defaultVariants: {\n      align: \"inline-start\",\n    },\n  },\n);\n\nexport type InputGroupAddonProps = ViewProps &\n  VariantProps<typeof inputGroupAddonVariants>;\n\nconst InputGroupAddon = forwardRef<BridgeViewRef, InputGroupAddonProps>(\n  ({ className, align = \"inline-start\", children, ...props }, ref) => (\n    <View\n      ref={ref}\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      {...props}\n    >\n      {wrapTextChildren(\n        children,\n        \"font-sans text-sm font-medium text-muted-foreground\",\n      )}\n    </View>\n  ),\n);\nInputGroupAddon.displayName = \"InputGroupAddon\";\n\n// 원본 size 4종 — rounded-［calc（var（--radius）-5px）］ → rounded-sm 근사（머리 주석）.\nconst inputGroupButtonVariants = cva(\"flex-row items-center gap-2\", {\n  variants: {\n    size: {\n      xs: \"h-6 gap-1 rounded-sm px-2\",\n      sm: \"h-8 gap-1.5 rounded-md px-2.5\",\n      \"icon-xs\": \"size-6 rounded-sm p-0\",\n      \"icon-sm\": \"size-8 rounded-md p-0\",\n    },\n  },\n  defaultVariants: {\n    size: \"xs\",\n  },\n});\n\nexport type InputGroupButtonProps = Omit<ButtonProps, \"size\"> &\n  VariantProps<typeof inputGroupButtonVariants>;\n\nconst InputGroupButton = forwardRef<BridgePressableRef, InputGroupButtonProps>(\n  ({ className, variant = \"ghost\", size = \"xs\", ...props }, ref) => (\n    // Button 기본 size（default） 토큰（h-10 px-4）은 cn/twMerge가 이 값으로 대체한다.\n    <Button\n      ref={ref}\n      variant={variant}\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      {...props}\n    />\n  ),\n);\nInputGroupButton.displayName = \"InputGroupButton\";\n\nexport type InputGroupTextProps = ViewProps;\n\nconst InputGroupText = forwardRef<BridgeViewRef, InputGroupTextProps>(\n  ({ className, children, ...props }, ref) => (\n    <View\n      ref={ref}\n      className={cn(\"flex-row items-center gap-2\", className)}\n      {...props}\n    >\n      {/* 이중 지정: 전경색은 내부 Text에（네이티브 색 상속 불가） */}\n      <Text className=\"font-sans text-sm text-muted-foreground\">\n        {children}\n      </Text>\n    </View>\n  ),\n);\nInputGroupText.displayName = \"InputGroupText\";\n\nexport type InputGroupInputProps = InputProps;\n\nconst InputGroupInput = forwardRef<BridgeTextInputRef, InputGroupInputProps>(\n  ({ className, ...props }, ref) => {\n    const group = useContext(InputGroupContext);\n    return (\n      <Input\n        ref={ref}\n        {...props}\n        className={cn(\n          \"h-full min-w-0 flex-1 rounded-none border-0 bg-transparent px-3 web:shadow-none\",\n          // 포커스 링 근사 — 그룹 이동 불가로 컨트롤 자체 슬림 링（머리 주석）.\n          \"focus:ring-1 focus:ring-ring focus:ring-offset-0\",\n          // 애던 정렬에 따른 패딩 보정（원본 ［&>input］:pl-2 등 치환）.\n          group?.inlineStart && \"pl-2\",\n          group?.inlineEnd && \"pr-2\",\n          group?.blockStart && \"pb-3\",\n          group?.blockEnd && \"pt-3\",\n          className,\n        )}\n      />\n    );\n  },\n);\nInputGroupInput.displayName = \"InputGroupInput\";\n\nexport type InputGroupTextareaProps = TextareaProps;\n\nconst InputGroupTextarea = forwardRef<\n  BridgeTextInputRef,\n  InputGroupTextareaProps\n>(({ className, ...props }, ref) => {\n  const group = useContext(InputGroupContext);\n  return (\n    <Textarea\n      ref={ref}\n      {...props}\n      className={cn(\n        \"min-h-0 min-w-0 flex-1 resize-none rounded-none border-0 bg-transparent px-3 py-3 web:shadow-none\",\n        \"focus:ring-1 focus:ring-ring focus:ring-offset-0\",\n        group?.inlineStart && \"pl-2\",\n        group?.inlineEnd && \"pr-2\",\n        group?.blockStart && \"pb-3\",\n        group?.blockEnd && \"pt-3\",\n        className,\n      )}\n    />\n  );\n});\nInputGroupTextarea.displayName = \"InputGroupTextarea\";\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/input-group.tsx"
    }
  ],
  "type": "registry:ui"
}
