You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.5 KiB
175 lines
5.5 KiB
"use client"; |
|
|
|
import { useState } from "react"; |
|
|
|
interface LlmBlockProps { |
|
/** Путь страницы, например "/foundation/colors" */ |
|
path: string; |
|
/** Версия данных, например "v2.1" */ |
|
version: string; |
|
/** Плоский текст для копирования */ |
|
specText: string; |
|
/** Содержимое блока — таблицы, правила */ |
|
children: React.ReactNode; |
|
} |
|
|
|
/** |
|
* LlmBlock — переиспользуемый блок LLM-спецификации. |
|
* Добавляется в конец каждой страницы брендбука, содержащей дизайн-стандарты. |
|
* Требование: ФТ-03-LLM (TZ.md) · docs/LLM_CONTEXT.md |
|
*/ |
|
export function LlmBlock({ path, version, specText, children }: LlmBlockProps) { |
|
const [copied, setCopied] = useState(false); |
|
|
|
function handleCopy(e: React.MouseEvent) { |
|
e.preventDefault(); |
|
navigator.clipboard.writeText(specText); |
|
setCopied(true); |
|
setTimeout(() => setCopied(false), 2000); |
|
} |
|
|
|
return ( |
|
<details |
|
open |
|
className="rounded-xl overflow-hidden" |
|
style={{ |
|
border: "2px dashed var(--brand-053m)", |
|
}} |
|
> |
|
{/* Заголовок */} |
|
<summary |
|
className="flex items-center justify-between px-5 py-3 cursor-pointer select-none list-none" |
|
style={{ background: "rgba(126,207,202,0.07)" }} |
|
> |
|
<div className="flex items-center gap-2 min-w-0"> |
|
<span |
|
className="shrink-0 text-[10px] font-bold px-1.5 py-0.5 rounded tracking-wider" |
|
style={{ background: "var(--brand-053m)", color: "#fff" }} |
|
> |
|
LLM |
|
</span> |
|
<span className="font-semibold text-sm" style={{ color: "var(--bb-text)" }}> |
|
LLM-спецификация |
|
</span> |
|
<span |
|
className="text-xs truncate hidden sm:inline" |
|
style={{ color: "var(--bb-text-muted)" }} |
|
> |
|
· машиночитаемые данные · docs/LLM_CONTEXT.md |
|
</span> |
|
</div> |
|
|
|
<div className="flex items-center gap-3 shrink-0 ml-3"> |
|
<span |
|
className="text-[10px] font-mono hidden md:inline" |
|
style={{ color: "var(--bb-text-muted)" }} |
|
> |
|
{path} · {version} |
|
</span> |
|
<button |
|
onClick={handleCopy} |
|
className="text-xs px-3 py-1 rounded font-medium transition-colors shrink-0" |
|
style={{ |
|
background: copied ? "#d1fae5" : "var(--brand-053m)", |
|
color: copied ? "#065f46" : "#fff", |
|
}} |
|
> |
|
{copied ? "✓ Скопировано" : "Скопировать"} |
|
</button> |
|
</div> |
|
</summary> |
|
|
|
{/* Содержимое */} |
|
<div |
|
className="px-5 py-5 space-y-6 border-t" |
|
style={{ |
|
borderColor: "var(--brand-053m)", |
|
borderStyle: "dashed", |
|
background: "var(--bb-content-bg)", |
|
}} |
|
> |
|
{children} |
|
</div> |
|
</details> |
|
); |
|
} |
|
|
|
/* ─── Утилиты для содержимого блока ──────────────────────────── */ |
|
|
|
/** Заголовок подсекции внутри LLM-блока */ |
|
export function LlmSection({ title }: { title: string }) { |
|
return ( |
|
<p |
|
className="text-[10px] font-semibold uppercase tracking-widest pb-1 border-b" |
|
style={{ color: "var(--bb-text-muted)", borderColor: "var(--bb-border)" }} |
|
> |
|
{title} |
|
</p> |
|
); |
|
} |
|
|
|
/** Компактная таблица для LLM-блока */ |
|
export function LlmTable({ |
|
headers, |
|
rows, |
|
}: { |
|
headers: string[]; |
|
rows: (string | React.ReactNode)[][]; |
|
}) { |
|
return ( |
|
<div className="overflow-x-auto"> |
|
<table className="w-full text-xs border-collapse font-mono"> |
|
<thead> |
|
<tr style={{ background: "var(--bb-sidebar-bg)" }}> |
|
{headers.map((h) => ( |
|
<th |
|
key={h} |
|
className="text-left px-2 py-1.5 font-medium border whitespace-nowrap" |
|
style={{ color: "var(--bb-text-muted)", borderColor: "var(--bb-border)" }} |
|
> |
|
{h} |
|
</th> |
|
))} |
|
</tr> |
|
</thead> |
|
<tbody> |
|
{rows.map((row, ri) => ( |
|
<tr key={ri} style={{ borderTop: `1px solid var(--bb-border)` }}> |
|
{row.map((cell, ci) => ( |
|
<td |
|
key={ci} |
|
className="px-2 py-1 border" |
|
style={{ |
|
borderColor: "var(--bb-border)", |
|
color: "var(--bb-text-muted)", |
|
maxWidth: "240px", |
|
}} |
|
> |
|
{cell} |
|
</td> |
|
))} |
|
</tr> |
|
))} |
|
</tbody> |
|
</table> |
|
</div> |
|
); |
|
} |
|
|
|
/** Список правил ✓ / ✕ */ |
|
export function LlmRules({ rules }: { rules: { ok: boolean; text: string }[] }) { |
|
return ( |
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-0.5"> |
|
{rules.map((r) => ( |
|
<div key={r.text} className="flex items-start gap-1.5 py-0.5 text-xs font-mono"> |
|
<span |
|
style={{ color: r.ok ? "#059669" : "#dc2626", fontWeight: 700, flexShrink: 0 }} |
|
> |
|
{r.ok ? "✓" : "✕"} |
|
</span> |
|
<span style={{ color: "var(--bb-text-muted)" }}>{r.text}</span> |
|
</div> |
|
))} |
|
</div> |
|
); |
|
}
|
|
|