# BreakdownTable

> A top-N table where each row carries a bar sized against the largest value, with share and change against the previous period.

Source: https://docs.nasaqui.com/components/breakdown-table

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/breakdown-table.json
```

BreakdownTable answers "what makes up this number?": traffic channels, source / medium, top pages, devices, traffic sources. Rows are sorted by value, each has a bar sized against the largest, a share of the total and, when `previous` is given, the change. Long lists collapse to `limit` rows with a "Show all" button.

## When to use

- Top-N lists in an analytics report.
- Any single-measure ranking that benefits from a bar.
- Adding extra columns with `columns`.

## When not to use

- Several measures per row that users sort: use [`DataTable`](https://docs.nasaqui.com/components/data-table).
- Countries with flags: use [`GeoList`](https://docs.nasaqui.com/components/geo-list).
- Search queries with CTR and position: use [`SearchPerformanceTable`](https://docs.nasaqui.com/components/search-performance-table).

## Import

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";

export function Channels() {
  return (
    <BreakdownTable
      title="Channels"
      dimensionLabel="Channel"
      valueLabel="Sessions"
      rows={[
        { id: "organic", label: "Organic Search", value: 28100, previous: 24800 },
        { id: "direct", label: "Direct", value: 13400, previous: 14100 },
      ]}
    />
  );
}
```

## Anatomy

```
BreakdownTable         data-slot="breakdown-table"  (a Card)
  Table                label, value, share, change and extra columns
    row bar            data-slot="breakdown-bar"
  Show all button      when there are more than `limit` rows
```

## API

### BreakdownTable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `rows` | `readonly BreakdownRow[]` | required | `{ id, label, value, previous?, href? }`. Sorted by value. |
| `dimensionLabel` | `ReactNode` | required | Heading of the first column. |
| `valueLabel` | `ReactNode` | required | Heading of the value column. |
| `title / description / action` | `ReactNode` | none | Card header. |
| `format` | `FormatNumberOptions` | none | Intl options for values. |
| `limit` | `number` | `8` | Rows shown before "Show all". |
| `showShare` | `boolean` | `true` | Share-of-total column (hidden on narrow screens). |
| `invert` | `boolean` | `false` | Down is good for the change column. |
| `color` | `string` | `var(--primary)` | Bar colour, a token. |
| `ltrLabels` | `boolean` | `false` | Keep labels left-to-right in RTL: URLs, paths, source / medium. |
| `columns` | `readonly BreakdownColumn[]` | none | Extra columns: `{ id, header, cell(row), align? }`. |
| `label` | `string` | `title` | Accessible name of the table. |
| `loading` | `boolean` | `false` | Skeleton rows. |
| `className` | `string` | none | Extra classes on the root. |
| `labels` | `Partial<BreakdownTableLabels>` | none | Replace any built-in English or Arabic string. |

## Examples

URLs stay left-to-right:

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";

export const Pages = () => <BreakdownTable title="Top pages" dimensionLabel="Page" valueLabel="Sessions" ltrLabels rows={[{ id: "a", label: "/docs/getting-started", value: 4210, href: "/docs/getting-started" }]} />;
```

An extra column:

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";

export const Videos = () => (
  <BreakdownTable
    title="Top videos"
    dimensionLabel="Video"
    valueLabel="Views"
    showShare={false}
    rows={[{ id: "v1", label: "RTL dashboard in 20 minutes", value: 18400 }]}
    columns={[{ id: "watch", header: "Watch time (h)", align: "end", cell: () => "612" }]}
  />
);
```

Arabic:

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";

export const Ar = () => <BreakdownTable title="القنوات" dimensionLabel="القناة" valueLabel="الجلسات" rows={[{ id: "o", label: "البحث المجاني", value: 28100, previous: 24800 }]} />;
```

## Accessibility

A real `table` with column headers and a name from `label` or `title`. Bars are decorative (`aria-hidden`); the values and shares are text. The change is a signed percentage, toned green or red, never colour alone. Links are ordinary anchors.

## RTL & i18n

- Columns and bars run from the inline start; the value columns align to the end.
- Set `ltrLabels` for URLs and source / medium so they are not reordered.
- Every figure uses the active locale with Western digits and is bidi-isolated (`Num`), so `+12.4%` keeps its order in Arabic.

## Styling & tokens

- Colours come from tokens (`--primary`, `--nq-success`, `--nq-warning`, `--nq-danger`, `--nq-tag-*`); never pass raw hex.
- Bar track uses `--nq-surface-soft`; up and down use `--nq-success-text` and `--nq-danger-text`.

## Do / Don't

- Do give `previous` so users see movement.
- Do use `ltrLabels` for paths and URLs.
- Don't use it for more than one measure.
- Don't set `limit` above about 15; link to a full report instead.

## Related

- [`geo-list`](https://docs.nasaqui.com/components/geo-list)
- [`table`](https://docs.nasaqui.com/components/table)
- [`data-table`](https://docs.nasaqui.com/components/data-table)
- [`metric-tiles`](https://docs.nasaqui.com/components/metric-tiles)

## Lab

https://docs.nasaqui.com/?path=/docs/components-analytics-breakdown-table--docs

## Code

### React

```tsx
import { BreakdownTable } from "@fadymondy/nasaq/web";

export function Channels() {
  return (
    <BreakdownTable
      title="Channels"
      dimensionLabel="Channel"
      valueLabel="Sessions"
      rows={[
        { id: "organic", label: "Organic Search", value: 28100, previous: 24800 },
        { id: "direct", label: "Direct", value: 13400, previous: 14100 },
      ]}
    />
  );
}
```

### shadcn

```tsx
import { BreakdownTable } from "@/components/ui/breakdown-table";

export function Channels() {
  return (
    <BreakdownTable
      title="Channels"
      dimensionLabel="Channel"
      valueLabel="Sessions"
      rows={[
        { id: "organic", label: "Organic Search", value: 28100, previous: 24800 },
        { id: "direct", label: "Direct", value: 13400, previous: 14100 },
      ]}
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqBreakdownTable } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqBreakdownTable
    title="Channels"
    dimension-label="Channel"
    value-label="Sessions"
    :rows="[
      { id: 'organic', label: 'Organic Search', value: 28100, previous: 24800 },
      { id: 'direct', label: 'Direct', value: 13400, previous: 14100 },
    ]"
  />
</template>
```

### Blade

```blade
<x-nq::breakdown-table
    title="Channels"
    dimension-label="Channel"
    value-label="Sessions"
    :rows="[
        ['id' => 'organic', 'label' => 'Organic Search', 'value' => 28100, 'previous' => 24800],
        ['id' => 'direct', 'label' => 'Direct', 'value' => 13400, 'previous' => 14100],
    ]"
/>
```

### HTML + Alpine

```html
<div data-slot="breakdown-table"      class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground">
            <div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-label text-foreground">Channels</h3></div>
        <div data-slot="card-content" class="px-0"><div data-slot="table-container" role="region" tabindex="0"  aria-label="Channels"     class="relative w-full overflow-x-auto outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus">
    <table data-slot="table" data-density="default"
                  class="w-full caption-bottom border-collapse text-body-sm">
        <thead data-slot="table-header" class="[&_tr]:border-b [&_tr]:hover:bg-transparent [&_tr]:even:bg-transparent"><tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="col" class="h-row text-start align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3">Channel</th>
                        <th data-slot="table-head" scope="col" class="h-row align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3 text-end">Sessions</th>
                        <th data-slot="table-head" scope="col" class="h-row align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3 hidden text-end sm:table-cell">Share</th>
                        <th data-slot="table-head" scope="col" class="h-row align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3 text-end">Change</th></tr></thead>
                <tbody data-slot="table-body" class="[&_tr:last-child]:border-0"><tr data-slot="table-row"     data-row="organic" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 min-w-40 max-w-0 sm:min-w-56"><span class="block truncate" dir="auto">Organic Search</span>
                                                                                                <span aria-hidden="true" data-slot="breakdown-bar" class="mt-1.5 block h-1.5 rounded-full bg-nq-surface-soft">
                                    <span class="block h-full rounded-full bg-[var(--bar)]" style="--bar: var(--primary); width: 100%"></span>
                                </span></td>
                            <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 text-end tabular-nums"><bdi data-slot="num" data-numeric="" class="tabular-nums">28,100</bdi></td>
                                                            <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 hidden text-end tabular-nums text-muted-foreground sm:table-cell"><bdi data-slot="num" data-numeric="" class="tabular-nums">67.7%</bdi></td>
                                                                                        <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 text-end tabular-nums text-nq-success-text"><bdi data-slot="num" data-numeric="">+13.3%</bdi></td></tr>
                                                                    <tr data-slot="table-row"     data-row="direct" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 min-w-40 max-w-0 sm:min-w-56"><span class="block truncate" dir="auto">Direct</span>
                                                                                                <span aria-hidden="true" data-slot="breakdown-bar" class="mt-1.5 block h-1.5 rounded-full bg-nq-surface-soft">
                                    <span class="block h-full rounded-full bg-[var(--bar)]" style="--bar: var(--primary); width: 47.6868%"></span>
                                </span></td>
                            <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 text-end tabular-nums"><bdi data-slot="num" data-numeric="" class="tabular-nums">13,400</bdi></td>
                                                            <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 hidden text-end tabular-nums text-muted-foreground sm:table-cell"><bdi data-slot="num" data-numeric="" class="tabular-nums">32.3%</bdi></td>
                                                                                        <td data-slot="table-cell" class="h-row align-middle whitespace-nowrap px-4 py-3 text-end tabular-nums text-nq-danger-text"><bdi data-slot="num" data-numeric="">-5%</bdi></td></tr></tbody>
    </table>
</div></div>
</div>
```
