Code Block

Split Compare

Две версии файла рядом: было слева, стало справа, строка напротив строки, а пустое место — явная заглушка, а не потерянный кусок кода.

  • diff
  • compare
  • split
  • review

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/codeblock-024

lib/total.ts
Было
export function total(items) {  let sum = 0  for (const item of items) {    sum += item.price  }  return sum}
Стало
export function total(items) {  return items.reduce(    (sum, item) => sum + item.price,    0,  ) }
Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «codeblock-024» (Split Compare) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/codeblock-024.json

Registry item: https://vibeui.ru/r/codeblock-024.json
Ставится в: components/vibeui/codeblock-024.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.

## 2. Что это
Две версии файла рядом: было слева, стало справа, строка напротив строки, а пустое место — явная заглушка, а не потерянный кусок кода.

Двухпанельное сравнение версий файла: строки выровнены по горизонтали, пропуски показаны заглушками. Ноль зависимостей, клиентского JS нет.

## 3. Как использовать
import { Codeblock024 } from "@/components/vibeui/codeblock-024"

<Codeblock024
  path="lib/total.ts"
  beforeLabel="Было"
  afterLabel="Стало"
/>

Полный список пропсов — в установленном файле.

## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- локальную палитру --vibeui-codeblock-024-* и не подменять её токенами вашей темы
- заглушки вместо отсутствующих строк: без них соседняя панель уезжает и сравнение ломается
- знак в подписи панели рядом с цветом: одна подложка не читается при дальтонизме
- счётчик номеров, пропускающий заглушки — иначе нумерация разъезжается с настоящим файлом
- правила раскладки на внутреннем shell: контейнерный запрос не применяется к самому контейнеру
- тёмную подложку блока — светлый моноширинный текст без неё не читается

## 6. Можно менять
- строки сравнения массивом rows
- путь к файлу через path
- подписи панелей через beforeLabel и afterLabel
- оттенки удаления и добавления под свою палитру

## 7. Правила
- Сопоставление строк — забота вызывающего кода: rows приходит уже выровненным парами.
- На узкой ширине панели встают друг под друга, и сравнение из горизонтального становится вертикальным.
- Изменение внутри строки не подсвечивается: помечается вся строка целиком.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

npx shadcn@latest add https://vibeui.ru/r/codeblock-024.json
https://vibeui.ru/r/codeblock-024.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-codeblock-024-*. Серверный: хуков нет. Панели получают один и тот же массив rows, поэтому строки всегда стоят напротив друг друга; отсутствующая строка рисуется заглушкой с другой подложкой, и счётчик номеров её пропускает — нумерация остаётся верной для каждой версии отдельно. Раскладка считается от собственной ширины: до 34rem панели встают друг под друга, дальше делят ширину пополам, и правила лежат на внутреннем [data-part="shell"].

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

import type { CSSProperties } from "react"

export type Codeblock024Row = {
  before: string | null
  after: string | null
}

export type Codeblock024Props = {
  path?: string
  beforeLabel?: string
  afterLabel?: string
  rows?: Codeblock024Row[]
  className?: string
  style?: CSSProperties
}

// Идея компонента: две версии файла рядом, строка напротив строки. Пустые
// места слева и справа — это не пропуск, а явная заглушка, поэтому глаз ведёт
// по одной горизонтали; на узкой ширине панели встают друг под друга.
const STYLES = `
:where([data-vibeui-block="codeblock-024"]){
--vibeui-codeblock-024-bg:oklch(0.19 0.014 265);
--vibeui-codeblock-024-head:oklch(0.23 0.018 265);
--vibeui-codeblock-024-fg:oklch(0.93 0.008 265);
--vibeui-codeblock-024-muted:oklch(0.66 0.016 265);
--vibeui-codeblock-024-gutter:oklch(0.5 0.02 265);
--vibeui-codeblock-024-border:oklch(1 0 0 / 12%);
--vibeui-codeblock-024-del:oklch(0.78 0.16 25);
--vibeui-codeblock-024-del-bg:oklch(0.5 0.16 25 / 18%);
--vibeui-codeblock-024-add:oklch(0.82 0.15 152);
--vibeui-codeblock-024-add-bg:oklch(0.5 0.14 152 / 18%);
--vibeui-codeblock-024-void:oklch(1 0 0 / 4%);
--vibeui-codeblock-024-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
--vibeui-codeblock-024-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="codeblock-024"]{
display:block;width:100%;max-width:44rem;box-sizing:border-box;margin:0;
overflow:hidden;border:1px solid var(--vibeui-codeblock-024-border);
border-radius:0.75rem;
background:var(--vibeui-codeblock-024-bg);color:var(--vibeui-codeblock-024-fg);
font-family:var(--vibeui-codeblock-024-font);
}
[data-vibeui-block="codeblock-024"] [data-part="head"]{
padding:0.5rem 0.875rem;
background:var(--vibeui-codeblock-024-head);
border-bottom:1px solid var(--vibeui-codeblock-024-border);
font-family:var(--vibeui-codeblock-024-mono);font-size:0.75rem;
color:var(--vibeui-codeblock-024-muted);
}
[data-vibeui-block="codeblock-024"] [data-part="shell"]{
display:grid;grid-template-columns:1fr;
}
[data-vibeui-block="codeblock-024"] [data-part="pane"]{min-width:0}
[data-vibeui-block="codeblock-024"] [data-part="pane"] + [data-part="pane"]{
border-top:1px solid var(--vibeui-codeblock-024-border);
}
[data-vibeui-block="codeblock-024"] [data-part="label"]{
display:flex;align-items:center;gap:0.375rem;
padding:0.3125rem 0.875rem;
border-bottom:1px solid var(--vibeui-codeblock-024-border);
font-size:0.6875rem;font-weight:650;letter-spacing:0.04em;text-transform:uppercase;
color:var(--vibeui-codeblock-024-muted);
}
[data-vibeui-block="codeblock-024"] [data-part="label"]::before{
content:"−";flex:none;color:var(--vibeui-codeblock-024-del);font-weight:700;
}
[data-vibeui-block="codeblock-024"] [data-side="after"] [data-part="label"]::before{
content:"+";color:var(--vibeui-codeblock-024-add);
}
[data-vibeui-block="codeblock-024"] pre{margin:0;padding:0.5rem 0;overflow-x:auto}
[data-vibeui-block="codeblock-024"] code{
display:block;min-width:max-content;
font-family:var(--vibeui-codeblock-024-mono);
font-size:0.75rem;line-height:1.75;white-space:pre;
counter-reset:line;
}
[data-vibeui-block="codeblock-024"] [data-part="row"]{
display:block;position:relative;padding:0 0.75rem 0 2.5rem;min-height:1.75em;
}
[data-vibeui-block="codeblock-024"] [data-part="row"]:not([data-void="true"])::before{
counter-increment:line;content:counter(line);
position:absolute;left:0;width:1.75rem;text-align:right;
color:var(--vibeui-codeblock-024-gutter);font-variant-numeric:tabular-nums;
user-select:none;-webkit-user-select:none;pointer-events:none;
}
/* Заглушка вместо строки: без неё соседняя панель уехала бы вверх и
   сравнение по горизонтали перестало бы работать. */
[data-vibeui-block="codeblock-024"] [data-part="row"][data-void="true"]{
background:var(--vibeui-codeblock-024-void);
}
[data-vibeui-block="codeblock-024"] [data-side="before"] [data-part="row"][data-changed="true"]{
background:var(--vibeui-codeblock-024-del-bg);
box-shadow:inset 0.125rem 0 0 var(--vibeui-codeblock-024-del);
}
[data-vibeui-block="codeblock-024"] [data-side="after"] [data-part="row"][data-changed="true"]{
background:var(--vibeui-codeblock-024-add-bg);
box-shadow:inset 0.125rem 0 0 var(--vibeui-codeblock-024-add);
}
@container (min-width: 34rem){
[data-vibeui-block="codeblock-024"] [data-part="shell"]{grid-template-columns:1fr 1fr}
[data-vibeui-block="codeblock-024"] [data-part="pane"] + [data-part="pane"]{
border-top:0;border-left:1px solid var(--vibeui-codeblock-024-border);
}
}
`

const ROWS: Codeblock024Row[] = [
  {
    before: "export function total(items) {",
    after: "export function total(items) {",
  },
  { before: "  let sum = 0", after: "  return items.reduce(" },
  {
    before: "  for (const item of items) {",
    after: "    (sum, item) => sum + item.price,",
  },
  { before: "    sum += item.price", after: "    0," },
  { before: "  }", after: "  )" },
  { before: "  return sum", after: null },
  { before: "}", after: "}" },
]

/** Две версии файла рядом: было слева, стало справа, строка напротив строки. */
export function Codeblock024({
  path = "lib/total.ts",
  beforeLabel = "Было",
  afterLabel = "Стало",
  rows = ROWS,
  className,
  style,
}: Codeblock024Props) {
  const panes = [
    { side: "before" as const, label: beforeLabel },
    { side: "after" as const, label: afterLabel },
  ]

  return (
    <>
      <style href="vibeui-codeblock-024" precedence="medium">
        {STYLES}
      </style>
      <figure
        data-vibeui-block="codeblock-024"
        className={className}
        style={style}
      >
        <figcaption data-part="head">{path}</figcaption>
        <div data-part="shell">
          {panes.map((pane) => (
            <div key={pane.side} data-part="pane" data-side={pane.side}>
              <div data-part="label">{pane.label}</div>
              <pre>
                <code>
                  {rows.map((row, index) => {
                    const text = row[pane.side]
                    const changed = row.before !== row.after

                    return (
                      <span
                        key={index}
                        data-part="row"
                        data-void={text === null || undefined}
                        data-changed={(changed && text !== null) || undefined}
                      >
                        {text ?? " "}
                      </span>
                    )
                  })}
                </code>
              </pre>
            </div>
          ))}
        </div>
      </figure>
    </>
  )
}