Code Block
Split Compare
Две версии файла рядом: было слева, стало справа, строка напротив строки, а пустое место — явная заглушка, а не потерянный кусок кода.
- diff
- compare
- split
- review
Превью
Использовать с AI
- 1. Скопируйте ссылку.
- 2. Напишите агенту своими словами и вставьте её в предложение.
- 3. Агент откроет ссылку и поставит компонент из registry.
размести это в шапке: https://vibeui.ru/c/codeblock-024
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.jsonhttps://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>
</>
)
}