# Icon

> Un ícono de lucide con los tamaños, los tonos y la semántica del sistema: decoración por defecto, imagen con nombre si tiene `label`.

```tsx
import { Icon } from "sebs7n-ui/icon"
```

Sin `"use client"`: sirve en un Server Component.

## Ejemplos

### Tamaños

16, 20 y 24px: los altos de línea del texto del sistema. El trazo escala con el tamaño, como en lucide.

```tsx
import { Icon } from "sebs7n-ui/icon"
import { SettingsIcon } from "lucide-react"

function Tamanos() {
  return (
    <div className="flex items-end gap-6">
      <div className="flex flex-col items-center gap-2">
        <Icon icon={SettingsIcon} size="sm" />
        <span className="text-footnote text-label-secondary">sm · 16</span>
      </div>
      <div className="flex flex-col items-center gap-2">
        <Icon icon={SettingsIcon} size="md" />
        <span className="text-footnote text-label-secondary">md · 20</span>
      </div>
      <div className="flex flex-col items-center gap-2">
        <Icon icon={SettingsIcon} size="lg" />
        <span className="text-footnote text-label-secondary">lg · 24</span>
      </div>
    </div>
  )
}
```

### Tonos

`current` hereda del texto y es el que va casi siempre. Los otros son para un ícono que habla solo.

```tsx
import { AlertTriangleIcon, CheckCircle2Icon, SearchIcon, SettingsIcon, SparklesIcon, XCircleIcon } from "lucide-react"
import { Icon } from "sebs7n-ui/icon"

function Tonos() {
  return (
    <div className="grid grid-cols-4 gap-4 sm:grid-cols-7">
      {(
        [
          ["current", SparklesIcon],
          ["muted", SearchIcon],
          ["subtle", SettingsIcon],
          ["brand", SparklesIcon],
          ["success", CheckCircle2Icon],
          ["warning", AlertTriangleIcon],
          ["danger", XCircleIcon],
        ] as const
      ).map(([tone, icon]) => (
        <div className="flex flex-col items-center gap-2" key={tone}>
          <Icon icon={icon} size="lg" tone={tone} />
          <span className="text-footnote text-label-secondary">{tone}</span>
        </div>
      ))}
    </div>
  )
}
```

### Al lado de un texto

Sin `label`: el texto ya lo dice. El ícono va del mismo color que la palabra, así que el tono queda en `current`.

```tsx
import { AlertTriangleIcon, CheckCircle2Icon, DownloadIcon, PlusIcon, SettingsIcon } from "lucide-react"
import { Badge } from "sebs7n-ui/badge"
import { Button } from "sebs7n-ui/button"

function ConTexto() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        <PlusIcon />
        Nueva factura
      </Button>
      <Button variant="secondary">
        <DownloadIcon />
        Exportar
      </Button>
      <Button size="icon-md" variant="ghost" aria-label="Configuración">
        <SettingsIcon />
      </Button>
      <Badge color="green">
        <CheckCircle2Icon />
        Pagada
      </Badge>
      <Badge color="amber">
        <AlertTriangleIcon />
        Vence hoy
      </Badge>
    </div>
  )
}
```

### Con nombre accesible

Cuando el ícono es la única señal —no hay palabra al lado—, `label` lo vuelve una imagen con nombre.

```tsx
import { AlertTriangleIcon, CheckCircle2Icon, XCircleIcon } from "lucide-react"
import { Icon } from "sebs7n-ui/icon"

function ConNombre() {
  const filas = [
    ["Factura 0012", "Pagada", CheckCircle2Icon, "success"],
    ["Factura 0013", "Vence en 3 días", AlertTriangleIcon, "warning"],
    ["Factura 0014", "Rechazada", XCircleIcon, "danger"],
  ] as const
  return (
    <ul className="flex w-full max-w-sm flex-col divide-y divide-gray-400 rounded-surface border border-separator bg-surface">
      {filas.map(([nombre, estado, icon, tone]) => (
        <li className="flex items-center justify-between gap-3 px-4 py-3 text-callout" key={nombre}>
          <span className="text-label">{nombre}</span>
          <Icon icon={icon} label={estado} tone={tone} />
        </li>
      ))}
    </ul>
  )
}
```

### Dentro de un Input

Un ícono a la izquierda con padding en el input. `muted` para que no compita con el texto.

```tsx
import { Icon } from "sebs7n-ui/icon"
import { Input } from "sebs7n-ui/input"
import { SearchIcon } from "lucide-react"

function EnUnInput() {
  return (
    <div className="relative w-full max-w-sm">
      <Icon className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2" icon={SearchIcon} size="sm" tone="muted" />
      <Input aria-label="Buscar clientes" className="pl-9" placeholder="Buscar clientes…" />
    </div>
  )
}
```

### Estado vacío

`EmptyState` ya pone el cuadrito y el `aria-hidden`: se le pasa el ícono sin tamaño.

```tsx
import { Button } from "sebs7n-ui/button"
import { EmptyState } from "sebs7n-ui/empty-state"
import { InboxIcon, MailIcon } from "lucide-react"

function EnUnEstadoVacio() {
  return (
    <EmptyState
      action={
        <Button size="sm">
          <MailIcon />
          Enviar recordatorio
        </Button>
      }
      className="w-full max-w-md"
      description="Cuando un cliente te escriba, va a aparecer acá."
      icon={<InboxIcon />}
      title="Sin mensajes"
    />
  )
}
```

### Cambio de estado

Dos íconos que se reemplazan con un cruce, dentro de un `startTransition`: el navegador hace la animación, no JavaScript.

```tsx
import { BellIcon, BellRingIcon } from "lucide-react"
import { Button } from "sebs7n-ui/button"
import { Icon } from "sebs7n-ui/icon"
import { startTransition, useState, ViewTransition } from "react"

function CambioDeEstado() {
  const [activas, setActivas] = useState(false)
  return (
    <Button
      aria-pressed={activas}
      onClick={() => startTransition(() => setActivas((valor) => !valor))}
      variant="secondary"
    >
      {activas ? (
        <ViewTransition key="on" enter="icon-in" exit="icon-out" default="none">
          <Icon icon={BellRingIcon} tone="brand" />
        </ViewTransition>
      ) : (
        <ViewTransition key="off" enter="icon-in" exit="icon-out" default="none">
          <Icon icon={BellIcon} />
        </ViewTransition>
      )}
      {activas ? "Notificaciones activas" : "Activar notificaciones"}
    </Button>
  )
}
```

### Animados

Las utilidades de Tailwind alcanzan: `animate-spin` para esperar, `animate-pulse` para «hay algo nuevo». Con `prefers-reduced-motion` se quedan quietos.

```tsx
import { Button } from "sebs7n-ui/button"
import { Icon } from "sebs7n-ui/icon"
import { InfoIcon, Loader2Icon, Trash2Icon } from "lucide-react"

function Animados() {
  return (
    <div className="flex flex-wrap items-center gap-6">
      <span className="inline-flex items-center gap-2 text-callout text-label-secondary">
        <Icon className="animate-spin motion-reduce:animate-none" icon={Loader2Icon} />
        Sincronizando
      </span>
      <span className="inline-flex items-center gap-2 text-callout text-label-secondary">
        <Icon className="animate-pulse motion-reduce:animate-none" icon={InfoIcon} tone="brand" />
        Hay una versión nueva
      </span>
      <Button variant="destructive">
        <Icon className="transition-transform duration-150 group-hover:-rotate-12" icon={Trash2Icon} />
        Borrar
      </Button>
    </div>
  )
}
```

## Props

### Icon

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `icon` * | `LucideIcon` | — | El componente de lucide: `icon={SearchIcon}`. Va como referencia, no como elemento. |
| `label` | `string` | — | Nombre accesible. Con él, `role="img"`; sin él, `aria-hidden`. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | `sm` 16px · `md` 20px · `lg` 24px. |
| `tone` | `"brand" \| "success" \| "warning" \| "subtle" \| "current" \| "muted" \| "danger"` | `"current"` | `current` (hereda) · `muted` · `subtle` · `brand` · `success` · `warning` · `danger`. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

`*` obligatoria.

## Teclado

| Tecla | Qué hace |
|---|---|
| — | No es interactivo. Si se puede apretar, va adentro de un `Button`. |

## Accesibilidad

- Sin `label` es decoración: `aria-hidden="true"` y `focusable="false"`, así ni el lector ni Tab lo encuentran. Es lo correcto cuando al lado hay un texto que dice lo mismo.
- Con `label` es `role="img"` con nombre. Es **obligatorio** cuando el ícono es la única señal: un check de «pagada» sin la palabra al lado.
- Los tonos de estado (`success`, `warning`, `danger`) usan el 900 de su familia, que llega a 4,5:1 sobre la superficie en los dos temas. Pero el color solo no informa: acompañalo con `label` o con texto.
- Sin `"use client"`: sirve en un Server Component.

## Reglas de uso

- **No reemplaza a lucide en los componentes del paquete.** `Button`, `Badge`, `SidebarItem` y `EmptyState` ya dimensionan el `<svg>` que reciben: ahí va el ícono de lucide pelado (`<PlusIcon />`). `Icon` es para el ícono suelto —en un texto, en una celda, en un input—.
- `tone="current"` casi siempre. Un ícono al lado de una palabra es de la misma tinta que la palabra; el tono propio es para el que habla solo.
- Un tamaño por contexto: `sm` (16) con `copy-14` y botones chicos, `md` (20) con `copy-16` e ítems de lista, `lg` (24) con títulos y estados vacíos. No hay `xl`: un ícono más grande que 24px es una ilustración.
- Para animar, las utilidades de Tailwind (`animate-spin`, `animate-pulse`, `transition-transform`), siempre con `motion-reduce:animate-none`. Para cambiar un ícono por otro, `<ViewTransition>` con `key`: el cruce lo hace el navegador.
- El catálogo completo, con búsqueda, está en [Iconos](/docs/iconos).

## Relacionados

[button](/docs/components/button.md) · [badge](/docs/components/badge.md) · [empty-state](/docs/components/empty-state.md)
