---
title: "Animated Badge"
description: "Durumu değişen etiket. Durum değişince ikon döner, etiket kayar ve hap yeni genişliğine yumuşakça uyar."
---

<Callout type="info" title="Ne zaman kullanılır">
Ekran açıkken değişen durumlar: bekliyor → eşitleniyor → tamamlandı, canlı bağlantılar, artan veya azalan sayaçlar. Durumu sabit kalan listeler için [Badge](https://builders.ikas.com/tr/docs/app-development/ui-kit/components/badge) yeterlidir.
</Callout>

## Kurulum

```bash
npx shadcn@latest add @ikas/animated-badge
```

Registry tanımı yoksa önce [Kurulum](https://builders.ikas.com/tr/docs/app-development/ui-kit/installation) adımlarını tamamlayın.

## Örnekler

### Durumlar

`status`: neutral, info, success, warning, danger ve loading. Durum değişince ikon `ICON_SPRING` ile yerine oturur, etiket `SPRING` ile kayar. `loading` nötr tonda kalır ve döner.

**Örnek: Durumlar** (`animated-badge/statuses`) · [Canlı önizleme](https://builders.ikas.com/ui-preview/demo/animated-badge/statuses)

```tsx
import * as React from "react"

import { AnimatedBadge, type AnimatedBadgeStatus } from "@/components/ikas/animated-badge"
import { SegmentedControl } from "@/components/ikas/segmented-control"

const labels: Record<AnimatedBadgeStatus, string> = {
  neutral: "Taslak",
  info: "Zamanlandı",
  success: "Aktif",
  warning: "İnceleme bekliyor",
  danger: "Başarısız",
  loading: "Eşitleniyor",
}

export default function AnimatedBadgeStatuses() {
  const [status, setStatus] = React.useState<AnimatedBadgeStatus>("neutral")

  return (
    <div className="flex w-full max-w-lg flex-col items-center gap-6">
      <div className="flex h-10 items-center text-[26px]">
        <AnimatedBadge status={status} className="scale-[2]">
          {labels[status]}
        </AnimatedBadge>
      </div>
      <div className="flex justify-center self-stretch border-t pt-5">
        <SegmentedControl
          size="sm"
          mode="radio"
          aria-label="Durum"
          value={status}
          onValueChange={setStatus}
          options={(Object.keys(labels) as AnimatedBadgeStatus[]).map((value) => ({ value, label: value }))}
        />
      </div>
    </div>
  )
}
```

### Varyantlar

Hap, renkler ve boyutlar Badge ile aynıdır: `variant` soft, solid veya surface, `size="sm"` yoğun tablolar için. Animasyon yalnızca durum değiştiğinde oynar, ekrana ilk gelişte oynamaz.

**Örnek: Varyantlar** (`animated-badge/variants`) · [Canlı önizleme](https://builders.ikas.com/ui-preview/demo/animated-badge/variants)

```tsx
import * as React from "react"

import { Button } from "@/components/ui/button"
import { AnimatedBadge, type AnimatedBadgeStatus } from "@/components/ikas/animated-badge"

const steps: { status: AnimatedBadgeStatus; label: string }[] = [
  { status: "neutral", label: "Taslak" },
  { status: "loading", label: "Eşitleniyor" },
  { status: "success", label: "Eşitlendi" },
  { status: "warning", label: "3 ürün eşleşmedi" },
  { status: "danger", label: "Bağlantı koptu" },
]
const variants = ["soft", "solid", "surface"] as const

export default function AnimatedBadgeVariants() {
  const [step, setStep] = React.useState(0)
  const { status, label } = steps[step]

  return (
    <div className="flex w-full max-w-lg flex-col gap-4">
      <div className="grid grid-cols-[auto_1fr_1fr] items-center gap-x-4 gap-y-3">
        {variants.map((variant) => (
          <React.Fragment key={variant}>
            <span className="font-mono text-[11px] text-muted-foreground">{variant}</span>
            <AnimatedBadge variant={variant} status={status}>
              {label}
            </AnimatedBadge>
            <AnimatedBadge variant={variant} status={status} size="sm" media="dot">
              {label}
            </AnimatedBadge>
          </React.Fragment>
        ))}
      </div>
      <Button size="sm" variant="outline" className="self-start" onClick={() => setStep((s) => (s + 1) % steps.length)}>
        Sonraki adım
      </Button>
    </div>
  )
}
```

### Eşitleme listesi

Bir tablo satırındaki durum. Eşitleme sürerken `media="icon"` ile dönen ikon, sonuç gelince `media="dot"` ile sakin nokta.

**Örnek: Eşitleme listesi** (`animated-badge/sync-list`) · [Canlı önizleme](https://builders.ikas.com/ui-preview/demo/animated-badge/sync-list)

```tsx
import * as React from "react"
import { RefreshCwIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { AnimatedBadge, type AnimatedBadgeStatus } from "@/components/ikas/animated-badge"

type Row = { id: string; name: string; status: AnimatedBadgeStatus; label: string }

const initial: Row[] = [
  { id: "IK-2041", name: "Pamuklu tişört", status: "neutral", label: "Bekliyor" },
  { id: "IK-2042", name: "Keten gömlek", status: "neutral", label: "Bekliyor" },
  { id: "IK-2043", name: "Deri cüzdan", status: "neutral", label: "Bekliyor" },
  { id: "IK-2044", name: "Yün atkı", status: "neutral", label: "Bekliyor" },
]
const outcomes: Pick<Row, "status" | "label">[] = [
  { status: "success", label: "Eşitlendi" },
  { status: "warning", label: "Eşleşmedi" },
  { status: "success", label: "Eşitlendi" },
  { status: "danger", label: "Reddedildi" },
]

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

export default function AnimatedBadgeSyncList() {
  const [rows, setRows] = React.useState(initial)
  const [running, setRunning] = React.useState(false)
  const patch = (index: number, next: Pick<Row, "status" | "label">) => setRows((current) => current.map((row, i) => (i === index ? { ...row, ...next } : row)))

  async function run() {
    setRunning(true)
    setRows(initial)
    await wait(400)
    for (let i = 0; i < initial.length; i++) {
      patch(i, { status: "loading", label: "Eşitleniyor" })
      await wait(260)
    }
    await wait(500)
    for (let i = 0; i < initial.length; i++) {
      patch(i, outcomes[i])
      await wait(220)
    }
    setRunning(false)
  }

  return (
    <Card className="w-full max-w-md gap-0 py-0">
      <ul className="divide-y text-sm">
        {rows.map((row) => (
          <li key={row.id} className="flex items-center gap-3 px-4 py-2.5">
            <span className="w-16 font-mono text-xs text-muted-foreground">{row.id}</span>
            <span className="flex-1 truncate">{row.name}</span>
            <AnimatedBadge status={row.status} media={row.status === "loading" ? "icon" : "dot"}>
              {row.label}
            </AnimatedBadge>
          </li>
        ))}
      </ul>
      <div className="border-t p-3">
        <Button size="sm" variant="outline" loading={running} onClick={run}>
          <RefreshCwIcon data-icon="inline-start" data-anim="spin" />
          Eşitlemeyi başlat
        </Button>
      </div>
    </Card>
  )
}
```

### Canlı durumlar

`pulse`, noktadan 1.6 saniyede bir halka açar. Yalnızca canlı durumlar (yayında, akış açık) için kullanın; süren işler için `loading` daha doğrudur.

**Örnek: Canlı durumlar** (`animated-badge/live`) · [Canlı önizleme](https://builders.ikas.com/ui-preview/demo/animated-badge/live)

```tsx
import { AnimatedBadge } from "@/components/ikas/animated-badge"

export default function AnimatedBadgeLive() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <AnimatedBadge status="success" media="dot" pulse>
        Canlı
      </AnimatedBadge>
      <AnimatedBadge status="info" media="dot" pulse>
        Akış açık
      </AnimatedBadge>
      <AnimatedBadge status="loading">Eşitleniyor</AnimatedBadge>
    </div>
  )
}
```

### Sayaç

`children` olarak dizi verirseniz yalnızca değişen parça kayar: sayı değişir, birim yerinde kalır.

**Örnek: Sayaç** (`animated-badge/counter`) · [Canlı önizleme](https://builders.ikas.com/ui-preview/demo/animated-badge/counter)

```tsx
import * as React from "react"
import { MinusIcon, PlusIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import { AnimatedBadge } from "@/components/ikas/animated-badge"

export default function AnimatedBadgeCounter() {
  const [count, setCount] = React.useState(3)

  return (
    <div className="flex items-center gap-3">
      <Button size="icon-sm" variant="outline" aria-label="Azalt" onClick={() => setCount((n) => Math.max(0, n - 1))}>
        <MinusIcon />
      </Button>
      {/* Two parts: only the number rolls, the unit stays put. */}
      <AnimatedBadge status={count === 0 ? "success" : "warning"} media="dot">
        {[count, " bekleyen"]}
      </AnimatedBadge>
      <Button size="icon-sm" variant="outline" aria-label="Artır" onClick={() => setCount((n) => Math.min(99, n + 1))}>
        <PlusIcon />
      </Button>
    </div>
  )
}
```

## Kullanım ilkeleri

**Yapın:**

- Durumu status ile verin; renk yalnızca anlam taşımayan kategoriler için.
- Aynı metin farklı bir anlama geliyorsa contentKey verin; aksi halde etiket kaymaz.
- Kendi ikonunuzu veriyorsanız ona durumla birlikte değişen bir key verin.

**Yapmayın:**

- Durumu hiç değişmeyen etiketler için kullanmayın; o iş Badge'in.
- pulse'ı süren işler için kullanmayın; loading zaten döner.
- Kendi keyframe'inizle rozeti yanıp söndürmeyin.

## Erişilebilirlik

- İkon ve nokta `aria-hidden` taşır; durumu etiket metni anlatır, bu yüzden etiketi her zaman yazın.
- `prefers-reduced-motion` açıkken ikon ve etiket yerinde çapraz geçişle değişir; kayma ve genişlik animasyonu oynamaz.

## API

### `AnimatedBadge`

`Badge`'in `variant`, `size` ve diğer prop'larını da alır.

| Prop | Tip | Varsayılan | Açıklama |
| --- | --- | --- | --- |
| `status` | `AnimatedBadgeStatus` | `"neutral"` | Durumun anlamı: neutral, info, success, warning, danger veya loading. Değişince ikon ve etiket akar. |
| `color` | `BadgeColor` | - | Palet rengi. status'u geçersiz kılar. |
| `media` | `AnimatedBadgeMedia` | `"icon"` | icon durum ikonunu, dot sakin liste noktasını gösterir; none yalnızca etiket. |
| `icon` | `React.ReactNode` | - | Durum ikonunun yerine geçer. Durumla birlikte değişen bir key verin ki ikon da dönsün. |
| `pulse` | `boolean` | `false` | Noktadan 1.6 saniyede bir halka açar. Yalnızca canlı durumlar için. |
| `contentKey` | `string \| number` | - | Etiketin kimliği. Varsayılan metnin kendisidir; aynı metin yeni bir anlam taşıyorsa key verin. |
| `children` | `React.ReactNode \| React.ReactNode[]` | - | Etiket. Dizi verirseniz yalnızca değişen parça kayar; ör. sayı kayar, birim yerinde kalır. |
