0198947c4e
- Создан компонент components/llm/LlmBlock.tsx (use client):
LlmBlock, LlmSection, LlmTable, LlmRules — переиспользуемый
паттерн для LLM-спецификаций на всех страницах брендбука
- /foundation/colors: добавлен раздел «LLM-спецификация» (v2.1)
· таблица фирменных цветов Oracal (7 шт.)
· таблица цветов сайта oclinica.ru (11 шт., +3 новых: коралловый,
светло-жёлтый, светло-зелёный)
· таблица контрастности WCAG 2.1
· правила применения ✓/✕
· кнопка «Скопировать» — plain text для LLM
- /foundation/typography: добавлен раздел «LLM-спецификация» (v2.0)
· таблица шрифтов DINPro vs Fira Sans
· шкала DINPro (6 стилей)
· шкала Fira Sans (11 стилей, включая letter-spacing)
· применение по носителям + правила ✓/✕
- docs/LLM_CONTEXT.md: создан сводный машиночитаемый файл бренда
Версия 2.1: все цвета, типографика, логотип, оффлайн, CSS-vars,
правила — единый контекст для AI при работе с брендом клиники
- docs/TZ.md: добавлено требование ФТ-03-LLM
- docs/SPRINTS.md: задачи LLM-блоков в Sprint 3–8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
176 lines
5.5 KiB
TypeScript
176 lines
5.5 KiB
TypeScript
"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>
|
||
);
|
||
}
|