{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-input-group",
  "type": "registry:component",
  "title": "Input group",
  "description": "A field and its addons inside one bordered, focus-ringed row",
  "author": "sudo-kw <https://github.com/sudo-kw>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "effect@4.1.2",
    "react@^19.2.4"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/components/src/field.tsx",
      "target": "@ui/gpuixui/components/field.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { InputProps, Props } from \"@gpuix/react\";\nimport {\n  mergeStyles,\n  resolveStyle,\n  type StateStyle,\n} from \"@gpuix/react/floating\";\nimport {\n  cn,\n  FIELD_DESCRIPTION,\n  FIELD_ERROR,\n  FIELD_INPUT,\n  FIELD_LABEL,\n  FIELD_LABEL_ROW,\n  FIELD_REQUIRED,\n  FIELD_ROOT,\n} from \"../tw/index.js\";\nimport * as Option from \"effect/Option\";\nimport { createContext, useContext, useState } from \"react\";\nimport { useFieldsetDisabled } from \"./fieldset.js\";\nimport {\n  fieldHelp,\n  fieldHint,\n  hintHost,\n  renderFieldHint,\n} from \"../primitives/components/hint.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  controlHeight,\n  keepContent,\n  keyboardRing,\n  nativeTextTheme,\n  selectionWash,\n  statusTone,\n  useTheme,\n} from \"./internal/theme.js\";\n\ninterface FieldContextValue {\n  readonly label: string;\n  readonly description: string | undefined;\n  readonly error: string | undefined;\n  readonly required: boolean;\n}\n\nconst FieldContext = createContext<FieldContextValue | undefined>(undefined);\n\nexport interface FieldProps extends Props {\n  readonly label: string;\n\n  readonly description?: string;\n\n  readonly error?: string;\n  readonly required?: boolean;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Field = ({\n  label,\n  description,\n  error,\n  required = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldProps) => {\n  const theme = useTheme();\n  const context: FieldContextValue = { label, description, error, required };\n  assertParts(fieldRoot, children);\n\n  return (\n    <FieldContext.Provider value={context}>\n      <div\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELD_ROOT, className), theme),\n          style,\n        )}\n      >\n        <FieldLabel required={required}>{label}</FieldLabel>\n        {children}\n        {error === undefined ? (\n          description === undefined ? null : (\n            <FieldDescription>{description}</FieldDescription>\n          )\n        ) : (\n          <FieldError>{error}</FieldError>\n        )}\n      </div>\n    </FieldContext.Provider>\n  );\n};\n\nexport interface FieldLabelProps extends Props {\n  readonly required?: boolean;\n\n  readonly className?: string;\n}\n\nexport const FieldLabel = ({\n  required = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldLabelProps) => {\n  const theme = useTheme();\n  return (\n    <div style={recipeStyle(FIELD_LABEL_ROW, theme)}>\n      <text\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELD_LABEL, className), theme),\n          style,\n        )}\n      >\n        {children}\n      </text>\n      {required ? (\n        <text\n          aria-label=\"Required\"\n          style={{\n            ...recipeStyle(FIELD_REQUIRED, theme),\n            color: statusTone(theme, \"destructive\").foreground,\n          }}\n        >\n          *\n        </text>\n      ) : null}\n    </div>\n  );\n};\n\nexport interface FieldDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const FieldDescription = ({\n  className,\n  style,\n  ...rest\n}: FieldDescriptionProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(FIELD_DESCRIPTION, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface FieldErrorProps extends Props {\n  readonly className?: string;\n}\n\nexport const FieldError = ({ className, style, ...rest }: FieldErrorProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      role=\"alert\"\n      {...rest}\n      style={mergeStyles(\n        {\n          ...recipeStyle(cn(FIELD_ERROR, className), theme),\n          color: statusTone(theme, \"destructive\").foreground,\n        },\n        style,\n      )}\n    />\n  );\n};\n\nexport interface FieldInputProps extends Omit<InputProps, \"style\"> {\n  readonly invalid?: boolean;\n\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n\n  readonly style?: StateStyle<FieldInputState>;\n}\n\nexport interface FieldInputState {\n  readonly disabled: boolean;\n  readonly focused: boolean;\n  readonly invalid: boolean;\n}\n\nexport const FieldInput = ({\n  invalid,\n  disabled = false,\n  className,\n  onFocus,\n  onBlur,\n  style,\n  placeholder,\n  value,\n  onChange,\n  ...rest\n}: FieldInputProps) => {\n  const theme = useTheme();\n  const field = useContext(FieldContext);\n  const fieldsetDisabled = useFieldsetDisabled();\n  const [focused, setFocused] = useState(false);\n\n  const [draft, setDraft] = useState(\"\");\n  const text = value ?? draft;\n  const isInvalid = invalid ?? field?.error !== undefined;\n  const isDisabled = disabled || fieldsetDisabled;\n  const state: FieldInputState = {\n    disabled: isDisabled,\n    focused,\n    invalid: isInvalid,\n  };\n\n  const message = field?.error ?? field?.description;\n  const description = Option.match(Option.fromNullishOr(message), {\n    onNone: () => (field?.required === true ? \"Required\" : undefined),\n    onSome: (text) => (field?.required === true ? `${text}. Required` : text),\n  });\n\n  const help = fieldHelp(\n    { description: description ?? rest[\"aria-description\"], placeholder },\n    text,\n  );\n\n  const hostStyle = mergeStyles(\n    mergeStyles(recipeStyle(cn(FIELD_INPUT, className), theme), {\n      height: controlHeight(theme, \"md\"),\n      backgroundColor: isDisabled ? theme.colors.disabled : theme.colors.input,\n      color: isDisabled\n        ? theme.colors.disabledForeground\n        : theme.colors.inputForeground,\n      borderColor:\n        isInvalid && !isDisabled\n          ? theme.colors.destructive\n          : theme.colors.border,\n      ...(isDisabled ? { cursor: \"not-allowed\" } : {}),\n      ...(isDisabled ? {} : keyboardRing(theme, isInvalid)),\n      ...selectionWash(theme),\n      ...keepContent,\n    }),\n    resolveStyle(style, state),\n  );\n  const hint = fieldHint({ placeholder, disabled: isDisabled }, theme, text, {\n    origin: \"host\",\n    align: \"center\",\n    style: hostStyle,\n  });\n\n  return (\n    <div style={hintHost()}>\n      <input\n        {...rest}\n        theme={nativeTextTheme(theme, \"input\")}\n        value={value}\n        {...(isDisabled ? { readOnly: true, tabIndex: -1 } : {})}\n        {...(field === undefined ? {} : { \"aria-label\": field.label })}\n        {...(help === undefined ? {} : { \"aria-description\": help })}\n        onChange={(event) => {\n          if (value === undefined) setDraft(event.value ?? \"\");\n          onChange?.(event);\n        }}\n        onFocus={(event) => {\n          setFocused(true);\n          onFocus?.(event);\n        }}\n        onBlur={(event) => {\n          setFocused(false);\n          onBlur?.(event);\n        }}\n        style={hostStyle}\n      />\n      {Option.match(hint, {\n        onNone: () => null,\n        onSome: (painted) => renderFieldHint(painted, rest.testId),\n      })}\n    </div>\n  );\n};\n\nconst fieldParts: ReadonlyArray<PartComponent> = [\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldInput,\n];\n\nconst fieldRoot: PartsContract = {\n  parent: \"Field\",\n  family: fieldParts,\n  accepts: [FieldInput],\n  hint: \"Field renders its label and message itself; put only the control inside it.\",\n};\n\nexport const fieldMeta = {\n  name: \"Inline validation\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no aria-describedby in 0.11.0: the message is an aria-description, not an id reference\",\n    \"the control is a GPUix <input> painted by this package's fixed field recipe; @gpuixui/primitives' Input paints its own density-scaled chrome (ADR 0006), so it is not adopted here\",\n    \"not a form library: the caller owns the value and when validation runs\",\n  ],\n  docs: \"docs/field.mdx\",\n  example: \"examples/field.example.tsx\",\n  testFile: \"src/behaviour/field.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/fieldset.tsx",
      "target": "@ui/gpuixui/components/fieldset.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { cn, FIELDSET_LEGEND, FIELDSET_ROOT } from \"../tw/index.js\";\nimport { createContext, useContext } from \"react\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport { useTheme } from \"./internal/theme.js\";\n\ninterface FieldsetContextValue {\n  readonly disabled: boolean;\n}\n\nconst FieldsetContext = createContext<FieldsetContextValue | undefined>(\n  undefined,\n);\n\nexport const useFieldsetDisabled = (): boolean =>\n  useContext(FieldsetContext)?.disabled ?? false;\n\nexport interface FieldsetProps extends Omit<Props, \"children\"> {\n  readonly legend: string;\n\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Fieldset = ({\n  legend,\n  disabled = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldsetProps) => {\n  const theme = useTheme();\n  assertParts(fieldsetRoot, children);\n\n  return (\n    <FieldsetContext.Provider value={{ disabled }}>\n      <div\n        role=\"group\"\n        aria-label={legend}\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELDSET_ROOT, className), theme),\n          style,\n        )}\n      >\n        <FieldsetLegend disabled={disabled}>{legend}</FieldsetLegend>\n        {children}\n      </div>\n    </FieldsetContext.Provider>\n  );\n};\n\nexport interface FieldsetLegendProps extends Props {\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n}\n\nexport const FieldsetLegend = ({\n  disabled = false,\n  className,\n  style,\n  ...rest\n}: FieldsetLegendProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(FIELDSET_LEGEND, className), theme), {\n          color: disabled\n            ? theme.colors.disabledForeground\n            : theme.colors.foreground,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nconst fieldsetParts: ReadonlyArray<PartComponent> = [Fieldset, FieldsetLegend];\n\nconst fieldsetRoot: PartsContract = {\n  parent: \"Fieldset\",\n  family: fieldsetParts,\n  accepts: [],\n  hint: \"Fieldset renders its legend itself; put Field, or any other control, inside it.\",\n};\n\nexport const fieldsetMeta = {\n  name: \"Fieldset\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no fieldset element and no aria-disabled in 0.11.0: the group is role=group named by its legend, and disabled reaches only FieldInput\",\n    \"only the legend dims with disabled; controls that do not read the context keep their own disabled prop\",\n  ],\n  docs: \"docs/fieldset.mdx\",\n  example: \"examples/fieldset.example.tsx\",\n  testFile: \"src/behaviour/fieldset.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/input-group.tsx",
      "target": "@ui/gpuixui/components/input-group.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { EventPayload, StyleDesc, PublicInstance } from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport { mergeStyles, resolveStyle } from \"@gpuix/react/floating\";\nimport { Input, type InputProps } from \"../primitives/components/Input.js\";\nimport { TextArea, type TextAreaProps } from \"../primitives/components/TextArea.js\";\nimport {\n  INPUT_GROUP,\n  INPUT_GROUP_ADDONS,\n  INPUT_GROUP_FOCUS,\n  INPUT_GROUP_INVALID,\n  INPUT_GROUP_ROW,\n  INPUT_GROUP_TEXT,\n  cn,\n} from \"../tw/index.js\";\nimport * as Arr from \"effect/Array\";\nimport {\n  Children,\n  createContext,\n  isValidElement,\n  useContext,\n  useMemo,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { FieldDescription, FieldError, FieldLabel } from \"./field.js\";\nimport { Button, type ButtonProps } from \"./internal/button.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport { useMergedRef } from \"./internal/ref.js\";\nimport { keepContent, selectionWash, useTheme } from \"./internal/theme.js\";\n\nexport type InputGroupAlign = keyof typeof INPUT_GROUP_ADDONS;\n\ninterface InputGroupContextValue {\n  readonly label: string | undefined;\n  readonly help: string | undefined;\n  readonly field: React.RefObject<PublicInstance | null>;\n  readonly setFocused: (focused: boolean) => void;\n}\n\nconst InputGroupContext = createContext<InputGroupContextValue | undefined>(\n  undefined,\n);\n\nconst useInputGroup = (): InputGroupContextValue | undefined =>\n  useContext(InputGroupContext);\n\nexport interface InputGroupProps {\n  readonly label?: string;\n\n  readonly description?: string;\n\n  readonly error?: string;\n\n  readonly invalid?: boolean;\n\n  readonly className?: string;\n\n  readonly style?: StyleDesc;\n  readonly testId?: string;\n  readonly children?: PartChildren;\n}\n\nexport const InputGroup = ({\n  label,\n  description,\n  error,\n  invalid = false,\n  className,\n  style,\n  testId,\n  children,\n}: InputGroupProps) => {\n  const theme = useTheme();\n  const [focused, setFocused] = useState(false);\n  const field = useRef<PublicInstance | null>(null);\n  const isInvalid = invalid || error !== undefined;\n  assertParts(inputGroupRoot, children);\n\n  const context = useMemo<InputGroupContextValue>(\n    () => ({\n      label,\n      help: error ?? description,\n      field,\n      setFocused,\n    }),\n    [description, error, label],\n  );\n\n  const ring = mergeStyles(\n    isInvalid ? recipeStyle(INPUT_GROUP_INVALID, theme) : undefined,\n    focused ? recipeStyle(INPUT_GROUP_FOCUS, theme) : undefined,\n  );\n  const leaves = Children.toArray(children);\n  const pane = (slot: GroupPane): ReadonlyArray<ReactNode> =>\n    Arr.filter(leaves, (child) => paneOf(child) === slot);\n\n  return (\n    <InputGroupContext.Provider value={context}>\n      <div style={recipeStyle(\"flex flex-col gap-1 w-full\", theme)}>\n        {label === undefined ? null : <FieldLabel>{label}</FieldLabel>}\n        <div\n          testId={testId}\n          role=\"group\"\n          aria-label={label}\n          style={mergeStyles(\n            mergeStyles(recipeStyle(cn(INPUT_GROUP, className), theme), ring),\n            style,\n          )}\n        >\n          {pane(\"top\")}\n          <div style={recipeStyle(INPUT_GROUP_ROW, theme)}>\n            {pane(\"leading\")}\n            {pane(\"field\")}\n            {pane(\"trailing\")}\n          </div>\n          {pane(\"bottom\")}\n        </div>\n        {error === undefined ? (\n          description === undefined ? null : (\n            <FieldDescription>{description}</FieldDescription>\n          )\n        ) : (\n          <FieldError>{error}</FieldError>\n        )}\n      </div>\n    </InputGroupContext.Provider>\n  );\n};\n\ntype GroupPane = \"top\" | \"leading\" | \"field\" | \"trailing\" | \"bottom\";\n\nconst paneOf = (child: ReactNode): GroupPane => {\n  if (!isValidElement<{ align?: InputGroupAlign }>(child)) return \"field\";\n  if (child.type === InputGroupInput || child.type === InputGroupTextarea)\n    return \"field\";\n  if (child.type !== InputGroupAddon) return \"field\";\n  return child.props.align === \"block-start\"\n    ? \"top\"\n    : child.props.align === \"block-end\"\n      ? \"bottom\"\n      : child.props.align === \"inline-end\"\n        ? \"trailing\"\n        : \"leading\";\n};\n\nexport interface InputGroupAddonProps {\n  readonly align?: InputGroupAlign;\n\n  readonly className?: string;\n  readonly style?: StyleDesc;\n  readonly testId?: string;\n  readonly children?: ReactNode;\n}\n\nexport const InputGroupAddon = ({\n  align = \"inline-start\",\n  className,\n  style,\n  testId,\n  children,\n}: InputGroupAddonProps) => {\n  const theme = useTheme();\n  const group = useInputGroup();\n  const renderer = useGpuix().renderer;\n  const self = useRef<PublicInstance | null>(null);\n\n  const focusField = (event: EventPayload): void => {\n    const target = group?.field.current;\n    if (target === null || target === undefined) return;\n\n    if (self.current !== null && event.elementId !== self.current.id) return;\n    renderer?.focusElement?.(target.id);\n  };\n\n  return (\n    <div\n      ref={self}\n      testId={testId}\n      onClick={focusField}\n      style={mergeStyles(\n        recipeStyle(cn(INPUT_GROUP_ADDONS[align], className), theme),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type InputGroupButtonProps = ButtonProps;\n\nexport const InputGroupButton = ({\n  variant = \"ghost\",\n  size = \"sm\",\n  ...rest\n}: InputGroupButtonProps) => <Button {...rest} variant={variant} size={size} />;\n\nexport interface InputGroupTextProps {\n  readonly className?: string;\n  readonly style?: StyleDesc;\n  readonly children?: ReactNode;\n}\n\nexport const InputGroupText = ({\n  className,\n  style,\n  children,\n}: InputGroupTextProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      style={mergeStyles(\n        recipeStyle(cn(INPUT_GROUP_TEXT, className), theme),\n        style,\n      )}\n    >\n      {children}\n    </text>\n  );\n};\n\nconst bareField: StyleDesc = {\n  borderWidth: 0,\n  borderRadius: 0,\n  backgroundColor: \"transparent\",\n  boxShadow: [],\n  focusVisible: { outlineWidth: 0, outlineOffset: 0 },\n};\n\nconst fieldSlot: StyleDesc = {\n  display: \"flex\",\n  flexDirection: \"column\",\n  flexGrow: 1,\n  minWidth: 0,\n};\n\nexport interface InputGroupInputProps extends InputProps {\n  readonly className?: string;\n}\n\nexport const InputGroupInput = ({\n  className,\n  style,\n  onFocus,\n  onBlur,\n  label,\n  ref,\n  ...rest\n}: InputGroupInputProps) => {\n  const theme = useTheme();\n  const group = useInputGroup();\n  const setField = useMergedRef<PublicInstance | null>(\n    group?.field ?? { current: null },\n    ref,\n  );\n  return (\n    <div style={fieldSlot}>\n      <Input\n        {...rest}\n        ref={setField}\n        label={label ?? group?.label}\n        className={cn(\"bg-transparent\", className)}\n        onFocus={(event) => {\n          group?.setFocused(true);\n          onFocus?.(event);\n        }}\n        onBlur={(event) => {\n          group?.setFocused(false);\n          onBlur?.(event);\n        }}\n        style={(state) => ({\n          ...bareField,\n          ...selectionWash(theme),\n          ...keepContent,\n          ...resolveStyle(style, state),\n        })}\n      />\n    </div>\n  );\n};\n\nexport interface InputGroupTextareaProps extends TextAreaProps {\n  readonly className?: string;\n}\n\nexport const InputGroupTextarea = ({\n  className,\n  style,\n  onFocus,\n  onBlur,\n  label,\n  ref,\n  ...rest\n}: InputGroupTextareaProps) => {\n  const theme = useTheme();\n  const group = useInputGroup();\n  const setField = useMergedRef<PublicInstance | null>(\n    group?.field ?? { current: null },\n    ref,\n  );\n  return (\n    <div style={fieldSlot}>\n      <TextArea\n        {...rest}\n        ref={setField}\n        label={label ?? group?.label}\n        className={cn(\"bg-transparent\", className)}\n        onFocus={(event) => {\n          group?.setFocused(true);\n          onFocus?.(event);\n        }}\n        onBlur={(event) => {\n          group?.setFocused(false);\n          onBlur?.(event);\n        }}\n        style={(state) => ({\n          ...bareField,\n          ...selectionWash(theme),\n          ...keepContent,\n          ...resolveStyle(style, state),\n        })}\n      />\n    </div>\n  );\n};\n\nconst inputGroupParts: ReadonlyArray<PartComponent> = [\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n];\n\nconst inputGroupRoot: PartsContract = {\n  parent: \"InputGroup\",\n  family: inputGroupParts,\n  accepts: [\n    InputGroupAddon,\n    InputGroupButton,\n    InputGroupText,\n    InputGroupInput,\n    InputGroupTextarea,\n  ],\n  hint: \"Put InputGroupInput (or InputGroupTextarea) and its addons directly inside InputGroup.\",\n};\n\nexport const inputGroupMeta = {\n  name: \"Input group\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"the row's ring follows any focus, not keyboard focus: the runtime does not say how focus arrived, and an ancestor box cannot wear the focused field's `focusVisible`\",\n    \"the field's own outline, edge and fill are suppressed, so a group inside a `Field` reads the group's chrome only\",\n    \"the group lays its parts out by alignment itself — 0.11.0 has no `order` — so a part wrapped in a fragment is placed with the field\",\n  ],\n  docs: \"docs/input-group.mdx\",\n  example: \"examples/input-group.example.tsx\",\n  testFile: \"src/behaviour/input-group.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/internal/button.tsx",
      "target": "@ui/gpuixui/components/internal/button.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport type { ReactNode } from \"react\";\nimport {\n  Button as PrimitiveButton,\n  IconButton as PrimitiveIconButton,\n  actionOnColor as primitiveActionOnColor,\n  type ButtonVariant as PrimitiveVariant,\n} from \"../../primitives/button.js\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Schema from \"effect/Schema\";\nimport type { Theme } from \"./theme.js\";\n\nconst ButtonVariants = Schema.Literals([\n  \"primary\",\n  \"secondary\",\n  \"ghost\",\n  \"danger\",\n  \"link\",\n]);\n\nexport type ButtonVariant = typeof ButtonVariants.Type;\n\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ButtonProps extends Props {\n  readonly variant?: ButtonVariant;\n\n  readonly size?: ButtonSize;\n\n  readonly disabled?: boolean;\n\n  readonly loading?: boolean;\n\n  readonly loadingLabel?: string;\n\n  readonly reducedMotion?: boolean;\n\n  readonly leading?: ReactNode;\n\n  readonly trailing?: ReactNode;\n}\n\nexport interface IconButtonProps extends Props {\n  readonly label: string;\n\n  readonly size?: ButtonSize;\n  readonly disabled?: boolean;\n}\n\ninterface Look {\n  readonly variant: PrimitiveVariant;\n  readonly className: string;\n}\n\nconst LOOKS: Readonly<Record<ButtonVariant, Look>> = {\n  primary: { variant: \"primary\", className: \"\" },\n  secondary: { variant: \"secondary\", className: \"border border-strong\" },\n  ghost: { variant: \"ghost\", className: \"\" },\n  danger: { variant: \"destructive\", className: \"\" },\n\n  link: { variant: \"link\", className: \"underline underline-offset-4\" },\n};\n\nconst lookOf = (variant: unknown): Look =>\n  LOOKS[Schema.is(ButtonVariants)(variant) ? variant : \"secondary\"];\n\nexport const actionOnColor = (theme: Theme, variant: ButtonVariant): string =>\n  primitiveActionOnColor(theme, lookOf(variant).variant);\n\nexport const Button = ({\n  variant = \"secondary\",\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  \"aria-label\": ariaLabel,\n  ...rest\n}: ButtonProps) => {\n  const look = lookOf(variant);\n  return (\n    <PrimitiveButton\n      {...rest}\n      variant={look.variant}\n      size={size}\n      disabled={disabled}\n      label={ariaLabel}\n      className={cn(look.className)}\n      onActivate={onClick}\n      style={style}\n    />\n  );\n};\n\nexport const IconButton = ({\n  label,\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  children,\n  ...rest\n}: IconButtonProps) => (\n  <PrimitiveIconButton\n    {...rest}\n    icon={children}\n    variant=\"ghost\"\n    size={size}\n    disabled={disabled}\n    label={label}\n    className=\"text-muted-foreground hover:text-foreground\"\n    onActivate={onClick}\n    style={style}\n  />\n);\n"
    },
    {
      "path": "packages/components/src/internal/parts.ts",
      "target": "@ui/gpuixui/components/internal/parts.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as P from \"effect/Predicate\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\n\nexport type PartComponent = (props: never) => ReactNode;\n\nexport type PartChildren =\n  | ReactElement<unknown>\n  | boolean\n  | null\n  | undefined\n  | ReadonlyArray<PartChildren>;\n\nexport interface PartsContract {\n  readonly parent: string;\n\n  readonly family: ReadonlyArray<PartComponent>;\n\n  readonly accepts: ReadonlyArray<PartComponent>;\n\n  readonly hint?: string;\n}\n\nconst componentName = (type: unknown): string =>\n  P.isString(type)\n    ? type\n    : P.isFunction(type)\n      ? type.name || \"anonymous\"\n      : \"element\";\n\nconst fragmentChildren = (element: ReactElement<unknown>): ReactNode =>\n  hasChildren(element.props) ? element.props.children : undefined;\n\nconst hasChildren = (\n  value: unknown,\n): value is { readonly children?: ReactNode } =>\n  P.isObject(value) && \"children\" in value;\n\nconst leaves = (children: ReactNode): ReadonlyArray<unknown> =>\n  Arr.flatMap(Children.toArray(children), (child) =>\n    isValidElement<unknown>(child) && child.type === Fragment\n      ? leaves(fragmentChildren(child))\n      : [child],\n  );\n\nexport const assertParts = (\n  contract: PartsContract,\n  children: ReactNode,\n): void => {\n  Arr.forEach(leaves(children), (leaf) => {\n    if (!isValidElement(leaf)) return;\n    if (Arr.some(contract.accepts, (part) => part === leaf.type)) return;\n    if (!Arr.some(contract.family, (part) => part === leaf.type)) return;\n\n    throw new Error(\n      `${componentName(leaf.type)} cannot be used inside ${contract.parent}.` +\n        (contract.hint === undefined ? \"\" : ` ${contract.hint}`),\n    );\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/ref.ts",
      "target": "@ui/gpuixui/components/internal/ref.ts",
      "type": "registry:lib",
      "content": "import type { Ref, RefCallback } from \"react\";\nimport { useCallback } from \"react\";\n\nconst assign = <T>(ref: Ref<T> | undefined, value: T): void => {\n  if (ref === null || ref === undefined) return;\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n  ref.current = value;\n};\n\nexport const useMergedRef = <T>(\n  internal: Ref<T>,\n  caller: Ref<T> | undefined,\n): RefCallback<T> =>\n  useCallback(\n    (value: T) => {\n      assign(internal, value);\n      assign(caller, value);\n    },\n    [internal, caller],\n  );\n"
    },
    {
      "path": "packages/components/src/internal/theme.ts",
      "target": "@ui/gpuixui/components/internal/theme.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  brandTextOn,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  pointerWash,\n  selectionOn,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n  type BoxShadow,\n  type ElevationLevel,\n  type FindHighlightSource,\n  type NativeTextFill,\n  type PlatformName,\n  type PointerWashSurface,\n  type StatusTone,\n  type SyntaxColors,\n  type SyntaxRole,\n  type TextRole,\n  type Theme,\n  type ThemeColors,\n} from \"../../tokens/index.js\";\n\nexport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n};\nexport type {\n  BoxShadow,\n  ElevationLevel,\n  FindHighlightSource,\n  NativeTextFill,\n  PlatformName,\n  StatusTone,\n  SyntaxColors,\n  SyntaxRole,\n  TextRole,\n  Theme,\n};\nexport { ThemeProvider, useTheme } from \"../../tokens/react.js\";\n\nexport const keyboardRing = (theme: Theme, invalid = false): StyleDesc =>\n  controlFocusRing(theme, { invalid }) ?? {};\n\nexport interface ControlRingState {\n  readonly disabled?: boolean;\n\n  readonly invalid?: boolean;\n}\n\nexport const controlRing = (\n  theme: Theme,\n  state: ControlRingState = {},\n): StyleDesc => controlFocusRing(theme, state) ?? {};\n\nexport const pressWash = (theme: Theme, replaced: PointerWashSurface): string =>\n  pointerWash(theme, \"active\", replaced) ?? theme.colors.active;\n\nexport const selectionWash = (theme: Theme): StyleDesc => ({\n  selectionColor: theme.colors.selection,\n});\n\nexport const keepContent: StyleDesc = { keyboardFocusDim: false };\n\nexport const disabledFade = (\n  disabled: boolean,\n): { readonly opacity: number } | undefined =>\n  disabled ? { opacity: disabledOpacity } : undefined;\n\nconst Tones = Schema.Literals([\"info\", \"success\", \"warning\", \"danger\"]);\n\nexport type Tone = typeof Tones.Type;\n\nexport const toneOf = (value: unknown): Tone =>\n  Schema.is(Tones)(value) ? value : \"info\";\n\nexport interface ToneStyle {\n  readonly fg: string;\n  readonly bg: string;\n  readonly border: string;\n}\n\nconst TOKEN_TONE: Readonly<Record<Tone, StatusTone>> = {\n  info: \"info\",\n  success: \"success\",\n  warning: \"warning\",\n  danger: \"destructive\",\n};\n\nexport const tone = (theme: Theme, value: Tone): ToneStyle => {\n  const { background, border, foreground } = statusTone(\n    theme,\n    TOKEN_TONE[value],\n  );\n  return { bg: background, border, fg: foreground };\n};\n\nconst softRoles: Readonly<Record<Tone, keyof ThemeColors>> = {\n  info: \"infoSoft\",\n  success: \"successSoft\",\n  warning: \"warningSoft\",\n  danger: \"destructiveSoft\",\n};\n\nexport const messageLink = (theme: Theme, value: Tone): string =>\n  brandTextOn(theme, softRoles[value]);\n\nexport const messageSelection = (theme: Theme, value: Tone): string =>\n  selectionOn(theme, softRoles[value]);\n\nexport const toneSurface = (theme: Theme, value: Tone): StyleDesc => {\n  const { bg, border, fg } = tone(theme, value);\n  return {\n    backgroundColor: bg,\n    color: fg,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: border,\n  };\n};\n"
    }
  ],
  "docs": "[Input group](https://gpuixui.sudokw.com/components/input-group) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/components/input-group",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}