Charts

Rank Movers

A ranking where the position matters more than the value: the place gets its own column and each row shows the move since the previous snapshot.

  • chart
  • ranking
  • movement
  • top

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/chart-022?lang=en

Top pages this week
  1. 1Тарифы▲ 25240
  2. 2Каталог▼ 14810
  3. 3Главная▼ 13960
  4. 4Документация▲ 22180
  5. 5Блог▼ 11640
  6. 6Вакансии▼ 1720

Единица измерения: visits. Стрелка — изменение места с прошлой недели.

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "chart-022" (Rank Movers) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/chart-022.json

Registry item: https://vibeui.ru/r/chart-022.json
Installs to: components/vibeui/chart-022.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A ranking where the position matters more than the value: the place gets its own column and each row shows the move since the previous snapshot.

A ranking with places: the position number, the name, an arrow for the move since the previous snapshot, the value and a bar showing the share of the leader. The top three are highlighted by the colour of their number. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Chart022 } from "@/components/vibeui/chart-022"

<Chart022
  title="Top pages this week"
  unit="visits"
  rows={[{ label: "Pricing", value: 5240, wasRank: 3 }]}
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-chart-022-* palette — do not swap it for your theme tokens (bg-card, --chart-1 and the like)
- the place as its own column: a ranking is asked «which position», not «how much exactly»
- the move since the previous snapshot — without it a top list only answers «who is here now»
- the inverted sign of the move: climbing the ranking means a smaller place number
- the text beside the arrow: a direction carried by colour and glyph alone dies in print
- highlighting the top three through nth-child rather than a flag in the data

## 6. You may change
- the rows array: label, value and the previous place in wasRank
- title — the ranking heading
- unit — the unit named in the caption under the list
- showMoves — whether the movement column is shown
- the accent through the accent prop — it colours the bars and the leading numbers

## 7. Rules
- Rows are not sorted automatically: the caller sets the order and it must descend by value.
- Without wasRank the move renders as «no movement» rather than hiding: a new entry is then indistinguishable from a static one.
- Beyond eight rows a ranking becomes a list: fold the rest into «other».
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/chart-022.json
https://vibeui.ru/r/chart-022.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-chart-022-*. Серверный: хуков нет. Строка — сетка из номера, имени, движения и значения; полоса занимает всю ширину строки снизу, поэтому числа стоят ровным столбцом. Движение считается как разница прошлого места и текущего: вверх — это уменьшение номера, поэтому знак обратен привычному. Тройка лидеров выделена через nth-child, а не флагом в данных.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Chart022Row = {
  label: string
  value: number
  wasRank?: number
}

export type Chart022Props = Omit<
  ComponentPropsWithoutRef<"figure">,
  "children" | "title"
> & {
  title?: string
  rows?: Chart022Row[]
  unit?: string
  showMoves?: boolean
  accent?: string
}

// Идея компонента: рейтинг, в котором место важнее величины. Номер позиции
// стоит отдельной колонкой, а рядом — движение относительно прошлого замера:
// «был четвёртым, стал вторым». Без движения таблица топа отвечает только
// «кто сейчас», а спрашивают обычно «что изменилось».
const STYLES = `
:where([data-vibeui-block="chart-022"]){
--vibeui-chart-022-bg:oklch(1 0 0);
--vibeui-chart-022-fg:oklch(0.22 0.014 265);
--vibeui-chart-022-muted:oklch(0.55 0.014 265);
--vibeui-chart-022-border:oklch(0.91 0.006 265);
--vibeui-chart-022-track:oklch(0.95 0.004 265);
--vibeui-chart-022-accent:oklch(0.55 0.17 265);
--vibeui-chart-022-up:oklch(0.55 0.14 155);
--vibeui-chart-022-down:oklch(0.58 0.16 25);
--vibeui-chart-022-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="chart-022"]{
display:flex;flex-direction:column;gap:0.5rem;
width:100%;max-width:24rem;box-sizing:border-box;margin:0;padding:0.875rem;
background:var(--vibeui-chart-022-bg);
border:1px solid var(--vibeui-chart-022-border);border-radius:0.875rem;
color:var(--vibeui-chart-022-fg);font-family:var(--vibeui-chart-022-font);
}
[data-vibeui-block="chart-022"] [data-part="title"]{margin:0;font-size:0.875rem;font-weight:650}
[data-vibeui-block="chart-022"] ol{display:flex;flex-direction:column;gap:0.5rem;margin:0;padding:0;list-style:none}
/* Номер, имя, движение и значение живут в одной сетке, полоса занимает всю
   строку снизу: числа не разъезжаются между строками. */
[data-vibeui-block="chart-022"] [data-part="row"]{
display:grid;grid-template-columns:1.25rem 1fr auto auto;align-items:center;
gap:0.125rem 0.5rem;font-size:0.8125rem;
}
[data-vibeui-block="chart-022"] [data-part="rank"]{
font-weight:700;font-variant-numeric:tabular-nums;color:var(--vibeui-chart-022-muted);
}
[data-vibeui-block="chart-022"] li:nth-child(-n+3) [data-part="rank"]{color:var(--vibeui-chart-022-accent)}
[data-vibeui-block="chart-022"] [data-part="name"]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
[data-vibeui-block="chart-022"] [data-part="move"]{
font-size:0.6875rem;font-variant-numeric:tabular-nums;color:var(--vibeui-chart-022-muted);
}
[data-vibeui-block="chart-022"] [data-part="move"][data-dir="up"]{color:var(--vibeui-chart-022-up)}
[data-vibeui-block="chart-022"] [data-part="move"][data-dir="down"]{color:var(--vibeui-chart-022-down)}
[data-vibeui-block="chart-022"] [data-part="value"]{font-weight:650;font-variant-numeric:tabular-nums}
[data-vibeui-block="chart-022"] [data-part="track"]{
grid-column:2 / -1;height:0.375rem;border-radius:9999px;
background:var(--vibeui-chart-022-track);overflow:hidden;
}
[data-vibeui-block="chart-022"] [data-part="fill"]{
display:block;height:100%;border-radius:inherit;background:var(--vibeui-chart-022-accent);
}
[data-vibeui-block="chart-022"] li:not(:nth-child(-n+3)) [data-part="fill"]{
background:color-mix(in oklab,var(--vibeui-chart-022-accent) 45%,oklch(0.9 0.01 265));
}
[data-vibeui-block="chart-022"] [data-part="unit"]{margin:0;font-size:0.75rem;color:var(--vibeui-chart-022-muted)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="chart-022"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ROWS: Chart022Row[] = [
  { label: "Тарифы", value: 5240, wasRank: 3 },
  { label: "Каталог", value: 4810, wasRank: 1 },
  { label: "Главная", value: 3960, wasRank: 2 },
  { label: "Документация", value: 2180, wasRank: 6 },
  { label: "Блог", value: 1640, wasRank: 4 },
  { label: "Вакансии", value: 720, wasRank: 5 },
]

/**
 * Рейтинг с номерами мест и движением относительно прошлого замера.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Chart022({
  title = "Топ страниц за неделю",
  rows = DEFAULT_ROWS,
  unit = "визитов",
  showMoves = true,
  accent,
  className,
  style,
  ...props
}: Chart022Props) {
  const max = Math.max(...rows.map((row) => row.value), 1)

  const palette = {
    ...(accent ? { "--vibeui-chart-022-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-chart-022" precedence="medium">
        {STYLES}
      </style>
      <figure
        {...props}
        data-vibeui-block="chart-022"
        className={className}
        style={palette}
      >
        <figcaption data-part="title">{title}</figcaption>
        <ol>
          {rows.map((row, index) => {
            const rank = index + 1
            // Движение вверх — это уменьшение номера места, поэтому знак
            // считается наоборот привычному «больше значит лучше».
            const shift = row.wasRank ? row.wasRank - rank : 0
            const direction = shift > 0 ? "up" : shift < 0 ? "down" : "flat"

            return (
              <li key={row.label} data-part="row">
                <span data-part="rank">{rank}</span>
                <span data-part="name">{row.label}</span>
                {showMoves ? (
                  <span data-part="move" data-dir={direction}>
                    {direction === "up"
                      ? `▲ ${shift}`
                      : direction === "down"
                        ? `▼ ${Math.abs(shift)}`
                        : "— без движения"}
                  </span>
                ) : (
                  <span data-part="move" />
                )}
                <span data-part="value">{row.value}</span>
                <span data-part="track" aria-hidden="true">
                  <span
                    data-part="fill"
                    style={{
                      width: `${Math.max((row.value / max) * 100, 2)}%`,
                    }}
                  />
                </span>
              </li>
            )
          })}
        </ol>
        <p data-part="unit">
          Единица измерения: {unit}. Стрелка — изменение места с прошлой недели.
        </p>
      </figure>
    </>
  )
}