# InputGroup

> Un campo con cosas pegadas adentro —«$», «.com», la lupa, un ⌘K o un botón—, con la superficie y el foco de iCloud en el grupo.

```tsx
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "sebs7n-ui/input-group"
```

## Ejemplos

### Antes y después del valor

La moneda, el dominio o la lupa van adentro del campo: la superficie y el foco son del grupo, y un click en el texto enfoca el campo.

```tsx
import { InputGroup, InputGroupAddon, InputGroupInput } from "sebs7n-ui/input-group"
import { Kbd } from "sebs7n-ui"
import { SearchIcon } from "lucide-react"

function Basico() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupAddon>$</InputGroupAddon>
        <InputGroupInput aria-label="Importe" inputMode="decimal" placeholder="0,00" />
        <InputGroupAddon>ARS</InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupAddon>facturas.</InputGroupAddon>
        <InputGroupInput aria-label="Subdominio" placeholder="tu-empresa" />
        <InputGroupAddon>.com</InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupAddon>
          <SearchIcon />
        </InputGroupAddon>
        <InputGroupInput aria-label="Buscar comprobantes" placeholder="Buscar comprobantes" type="search" />
        <InputGroupAddon>
          <Kbd size="sm">⌘K</Kbd>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
```

### Con un botón

Un botón a escala del campo: `plain` para la acción, `ghost` para uno de ícono (con `aria-label`).

```tsx
import { CopyIcon } from "lucide-react"
import { Field, FieldLabel } from "sebs7n-ui"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "sebs7n-ui/input-group"

function ConBoton() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Field>
        <FieldLabel>Cupón de descuento</FieldLabel>
        <InputGroup>
          <InputGroupInput placeholder="VERANO26" />
          <InputGroupAddon>
            <InputGroupButton variant="plain">Aplicar</InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      </Field>
      <InputGroup>
        <InputGroupInput aria-label="Link de pago" readOnly value="https://pagos.example.com/f/0012" />
        <InputGroupAddon>
          <InputGroupButton aria-label="Copiar el link">
            <CopyIcon />
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
```

### Tamaños

28, 36 y 40, los mismos altos que los botones: un campo y un botón del mismo `size` miden lo mismo.

```tsx
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "sebs7n-ui/input-group"

function Tamanos() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      {(["sm", "md", "lg"] as const).map((size) => (
        <InputGroup key={size} size={size}>
          <InputGroupAddon>$</InputGroupAddon>
          <InputGroupInput aria-label={`Importe ${size}`} placeholder="0,00" />
          <InputGroupAddon>
            <InputGroupButton variant="plain">Cobrar</InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      ))}
    </div>
  )
}
```

### Botón esperando

`loading` en `InputGroupButton`: el spinner en el lugar del texto, el ancho no salta y el clic no pasa.

```tsx
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "sebs7n-ui/input-group"
import { TicketIcon } from "lucide-react"
import { useState } from "react"

function Cargando() {
  const [loading, setLoading] = useState(false)
  return (
    <div className="w-full max-w-sm">
      <InputGroup>
        <InputGroupAddon>
          <TicketIcon />
        </InputGroupAddon>
        <InputGroupInput aria-label="Cupón" defaultValue="BIENVENIDA" />
        <InputGroupAddon>
          <InputGroupButton
            loading={loading}
            onClick={() => {
              setLoading(true)
              setTimeout(() => setLoading(false), 1500)
            }}
            variant="plain"
          >
            Aplicar
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
```

## Props

### InputGroup

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `disabled` | `boolean` | — | Apaga el campo, los botones de adentro y la superficie (a .4). |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | `sm` 28, `md` 36 (default), `lg` 40. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### InputGroupAddon

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `onMouseDown` | `MouseEventHandler<T>` | — | Se llama antes de enfocar el campo. Con `event.preventDefault()`, el click no lo enfoca. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### InputGroupButton

Hereda las props de `Button`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `disabled` | `boolean` | — | Apaga la interacción y lo marca con `data-disabled`, que es el atributo del que cuelgan los estilos de apagado. |
| `loading` | `boolean` | `false` | Esperando (validando el cupón, buscando): como `Button`, el spinner en el lugar del contenido (el ancho no salta), `aria-busy` y el clic no pasa. |
| `onClick` | `MouseEventHandler<T>` | — | Se ignora mientras `loading` está activo. |
| `variant` | `"default" \| "plain" \| "ghost"` | `"ghost"` | `ghost` (default, neutro), `plain` (texto en el acento) o `default` (acento sólido). |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### InputGroupInput

Hereda las props de `Input`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `disabled` | `boolean` | — | Apaga la interacción y lo marca con `data-disabled`, que es el atributo del que cuelgan los estilos de apagado. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

## Teclado

| Tecla | Qué hace |
|---|---|
| Tab | Entra al campo y sigue a los botones de adentro, en el orden en que están. |

## Accesibilidad

- El nombre es el del campo: `FieldLabel` en un `Field`, o `aria-label` en `InputGroupInput`. Un addon de texto («$», «.com») **no** lo nombra: si hace falta que se lea, ponelo en el label («Importe en pesos»).
- Los íconos de un addon son decorativos; un `InputGroupButton` de solo ícono necesita `aria-label`.
- El foco se ve en el grupo (el anillo interior de 3 px, sin relleno) aunque lo tenga el `<input>` de adentro; el inválido (`aria-invalid` en el input) pone el borde rojo al grupo.
- Un click en un addon de texto o ícono enfoca el campo; en un botón, hace lo del botón.

## Reglas de uso

- **Para un dato con unidad o formato fijo** (moneda, dominio, prefijo) o una acción que es del campo (copiar, aplicar un cupón). Una acción de todo el formulario va afuera, en un `Button`.
- Los addons van en el orden del DOM: antes del `InputGroupInput` quedan a la izquierda, después a la derecha.
- `size` 28/36/40 como los botones; el botón de adentro escala solo (20/28/32).
- `InputGroupButton loading`: como `Button`, el spinner en el lugar del contenido (el ancho no salta), `aria-busy` y el clic no pasa. Para «Aplicar» un cupón mientras se valida.
- Para buscar con sugerencias es `Combobox` o `Autocomplete`, que ya usan esta misma superficie.
- Solo por subpath (`sebs7n-ui/input-group`): no está en el barrel, por peso.

## Relacionados

[input](/docs/components/input.md) · [field](/docs/components/field.md) · [kbd](/docs/components/kbd.md)
