# PhoneInput

> Un teléfono en E.164: el país con su bandera y código adentro del campo, y el número en dígitos. Sin libphonenumber.

```tsx
import { PhoneInput } from "sebs7n-ui/phone-input"
```

## Ejemplos

### Básico

El país con su bandera y código adentro del campo, y el número en dígitos. El valor es E.164.

```tsx
import { Field, FieldDescription, FieldLabel } from "sebs7n-ui"
import { PhoneInput } from "sebs7n-ui/phone-input"
import { useState } from "react"

function Basic() {
  const [phone, setPhone] = useState("+5491155552002")
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Teléfono de contacto</FieldLabel>
      <PhoneInput name="phone" onValueChange={setPhone} value={phone} />
      <FieldDescription>
        Se guarda como <code>{phone || "—"}</code>.
      </FieldDescription>
    </Field>
  )
}
```

### Con validación

`isValidPhone` (de `sebs7n-ui/lib/phone`, también en el servidor) mira el largo del número de cada país.

```tsx
import { Field, FieldError, FieldLabel } from "sebs7n-ui"
import { PhoneInput } from "sebs7n-ui/phone-input"
import { isValidPhone } from "sebs7n-ui/lib/phone"
import { useState } from "react"

function WithValidation() {
  const [phone, setPhone] = useState("+598991234")
  const invalid = phone !== "" && !isValidPhone(phone)
  return (
    <Field className="w-full max-w-sm" invalid={invalid}>
      <FieldLabel>Teléfono para avisos de cobro</FieldLabel>
      <PhoneInput aria-invalid={invalid} defaultCountry="UY" onValueChange={setPhone} value={phone} />
      <FieldError alert match={invalid}>Al número le faltan dígitos.</FieldError>
    </Field>
  )
}
```

### Para mostrar

`formatPhone` de `sebs7n-ui/lib/phone`: internacional legible, o nacional si el teléfono es argentino y quien lo lee también (`country: "AR"`).

```tsx
import { formatPhone } from "sebs7n-ui/lib/phone"

function Display() {
  return (
    <dl className="grid w-full max-w-md grid-cols-[1fr_auto_auto] overflow-x-auto gap-x-4 gap-y-2 text-callout">
      <dt className="text-label-secondary">Cliente</dt>
      <dt className="text-label-secondary">Internacional</dt>
      <dt className="text-label-secondary">Desde Argentina</dt>
      {contacts.map((contact) => (
        <div className="contents" key={contact.client}>
          <dd className="text-label">{contact.client}</dd>
          <dd className="whitespace-nowrap tabular-nums text-label">{formatPhone(contact.phone)}</dd>
          <dd className="whitespace-nowrap tabular-nums text-label">{formatPhone(contact.phone, { country: "AR" })}</dd>
        </div>
      ))}
    </dl>
  )
}
```

### Datos viejos

Un valor guardado sin «+» se toma como número nacional del país (`011 5555-2002` → +54 11 5555 2002) y el formulario ya lleva el E.164. Si no es un número posible, queda el texto y el campo inválido.

```tsx
import { Field, FieldDescription, FieldLabel } from "sebs7n-ui"
import { PhoneInput } from "sebs7n-ui/phone-input"

function Legacy() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Field>
        <FieldLabel>Teléfono de Acme S.A.</FieldLabel>
        <PhoneInput defaultValue="011 5555-2002" />
      </Field>
      <Field>
        <FieldLabel>Teléfono de Globex SRL</FieldLabel>
        <PhoneInput defaultValue="llamar a la tarde" />
        <FieldDescription>No es un número: corregilo a mano.</FieldDescription>
      </Field>
    </div>
  )
}
```

## Props

### PhoneInput

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `aria-describedby` | `string` | — | El `id` de la ayuda o del error del campo. |
| `aria-invalid` | `boolean` | — | Marca el campo inválido (borde rojo). |
| `aria-label` | `string` | — | Nombre accesible del elemento. |
| `aria-labelledby` | `string` | — | El `id` del elemento que nombra el número, si no es un `<label>`. |
| `defaultCountry` | `string` | `"AR"` | El país del selector si el valor no dice otro. Por defecto, «AR». |
| `defaultValue` | `string` | `""` | El valor inicial. Es la versión no controlada de `value`. |
| `disabled` | `boolean` | — | Apaga el selector y el número. |
| `id` | `string` | — | El `id` del campo del número, para un `<Label htmlFor>`. |
| `labels` | `Partial<Labels["phoneInput"]>` | — | Textos: `country` (antes del país, en el nombre del selector) y `unknownCode` (lo que se anuncia al pegar un código que no está). El idioma de los nombres es `countryPicker.locale`. |
| `locale` | `string` | — | El idioma de los nombres de la lista. Por defecto, el de `labels` (`countryPicker.locale`). |
| `name` | `string` | — | El nombre con el que el E.164 viaja en un formulario. |
| `onValueChange` | `(value: string) => void` | — | Avisa el teléfono en E.164, o vacío si se borró el número. |
| `placeholder` | `string` | — | Lo que dice el número vacío. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 28, 36 (default) o 40, como los campos. |
| `value` | `string` | — | El teléfono en E.164 («+5491155552002»); vacío sin número. Pasarlo lo vuelve controlado. Un valor sin «+» (un dato viejo) se toma como número nacional del país de `defaultCountry` (o internacional, si empieza con su código: «5491155552002»); si no es un número posible, se muestra tal cual y el campo queda inválido después de tocarlo o de enviar. Hasta que se edite, el form manda el valor original: migrar los datos es de la app. |
| `className` | `string` | — | Clases de la superficie. |

## Teclado

| Tecla | Qué hace |
|---|---|
| Tab | Entra al selector de país y después al número. |
| Enter · Espacio · ↓ | En el selector, abre la lista de países. |
| a–z | Con la lista abierta, salta al país que empieza así. |
| 0–9 | En el número, escriben; lo que no es un dígito no entra. |

## Accesibilidad

- El selector se llama «Código de país: Argentina (+54)» (`labels.country` más el país): la bandera es decorativa.
- El número es un `<input type="tel">` con `inputMode="tel"` (el teclado numérico en el teléfono): nombralo con un `FieldLabel` o `aria-label`.
- No se valida solo: para el error, `isValidPhone` y el `FieldError` del campo (con `alert` si se valida mientras se escribe).

## Reglas de uso

- El valor es E.164 (`+5491155552002`) y viaja así con `name`: se guarda tal cual y sirve para un link de WhatsApp.
- `isValidPhone` y `parsePhone` están en `sebs7n-ui/lib/phone`, sin `"use client"`: validan igual en una Server Action. Miran el **largo** del número de cada país, no el tipo de línea.
- Datos viejos: un `value`/`defaultValue` sin «+» («011 5555-2002») se toma como número nacional del país de `defaultCountry` (o internacional si empieza con su código: «5491155552002»); si no es un número posible, queda el texto como estaba y el campo va en rojo recién después de tocarlo o de enviar. Hasta que se edite, ni `onValueChange` ni el form cambian: el `name` manda el valor original. Migrar los datos es de la app.
- Para mostrar un teléfono guardado, `formatPhone(e164, { country })` de `sebs7n-ui/lib/phone`: «+54 9 11 5555-2002», o nacional («011 15-5555-2002») si es argentino y `country` es `"AR"`. Argentina sale con los formatos de libphonenumber (el área de 2, 3 o 4 dígitos parte al abonado: «+54 9 341 456-7890», «02478 15-40-9043»); el resto no es el formato oficial de cada país: siempre internacional, en grupos desde la derecha sin grupos de un dígito. Lo que no es un teléfono válido (`isValidPhone`) vuelve tal cual, también un número a medio escribir.
- El número se corta en el largo máximo del país. Pegar un número que empieza con «+» o «00» cambia el país solo; si el código no está en la tabla, el número no cambia y se anuncia «Código de país desconocido».
- El prefijo nacional no entra al E.164: «011 5555 2002» es `+541155552002` (Italia no tiene: su 0 se queda). En Argentina, el celular con 15 («11 15 5555 2002») pasa a la forma con 9, `+5491155552002`. `isValidPhone` rechaza un número que empieza con el prefijo nacional.
- Tiene 45 países (América, Europa y los más comunes); uno que falte se suma a la tabla de `lib/phone` con los largos de libphonenumber.
- No formatea mientras se escribe. Solo por subpath (`sebs7n-ui/phone-input`): no está en el barrel, por peso.

## Relacionados

[country-picker](/docs/components/country-picker.md) · [input-group](/docs/components/input-group.md) · [field](/docs/components/field.md)
