fix(buttons): переделаны кнопки под реальный сайт oclinica.ru

Анализ CSS сайта (style.css темы clinic_bootstrap_mobile) выявил
4 реальных типа кнопок — заменены ранее придуманные варианты:

- primary  → коралловый #FFA39C + shadow (форм-сабмит «Запишите меня!»)
- outline  → белый + бежевая рамка #BF9975 («Записаться на прием»)
- teal     → бирюзовый #60959c («Позвонить»)
- pill     → кремовый #e9e4d4 + radius 25px («Заказать звонок»)

Удалены: secondary, ghost, danger (не существуют на реальном сайте)
Добавлен раздел «CSS с сайта» с точными значениями
Добавлена таблица «Где применяется» с реальными CSS-классами сайта
LLM-блок обновлён до v2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AR 15 M4
2026-03-22 20:41:27 +05:00
parent 0855892643
commit c1731615ab
3 changed files with 373 additions and 202 deletions
+333 -194
View File
@@ -35,114 +35,184 @@ function Section({
); );
} }
const VARIANT_INFO = [ const VARIANTS = [
{ {
variant: "primary" as const, variant: "primary" as const,
label: "Primary", name: "Primary",
label: "Запишитесь к нам",
cssClass: ".bb-btn-primary", cssClass: ".bb-btn-primary",
bg: "#5b7b87", bg: "#FFA39C",
text: "#fff", border: "#FF847B",
description: "Основная кнопка призыва к действию. Тёмный бирюзово-серый фон, белый текст.", textColor: "#fff",
useCase: "Записаться · Подтвердить", radius: "7px",
shadow: "да",
where: "Кнопка отправки форм записи",
example: "«Запишите меня!»",
note: "Коралловый — самый заметный акцент на странице. Всегда один в форме.",
}, },
{ {
variant: "secondary" as const, variant: "outline" as const,
label: "Secondary", name: "Outline",
cssClass: ".bb-btn-secondary", label: "Записаться на приём",
bg: "прозрачный", cssClass: ".bb-btn-outline",
text: "#5b7b87", bg: "#fff",
description: "Второстепенное действие. Контурная кнопка с фирменным цветом.", border: "#BF9975",
useCase: "Узнать подробнее · Редактировать", textColor: "#BF9975",
radius: "7px",
shadow: "нет",
where: "Хедер, навигация, ссылки-кнопки",
example: "«Записаться на прием», «Все новости»",
note: "Бежевая рамка — ненавязчивый вторичный CTA. Не конкурирует с основной формой.",
}, },
{ {
variant: "ghost" as const, variant: "teal" as const,
label: "Ghost", name: "Teal",
cssClass: ".bb-btn-ghost", label: "Позвонить",
bg: "прозрачный", cssClass: ".bb-btn-teal",
text: "#5b7b87", bg: "#60959c",
description: "Третичное действие. Без фона и видимой рамки, текстовый акцент.", border: "прозрачный",
useCase: "Отмена · Назад · Ещё...", textColor: "#fff",
radius: "7px",
shadow: "нет",
where: "Контактные действия — звонок",
example: "«Позвонить»",
note: "Серо-бирюзовый — цвет из реального CSS сайта. Близок к Oracal 066M.",
}, },
{ {
variant: "danger" as const, variant: "pill" as const,
label: "Danger", name: "Pill",
cssClass: ".bb-btn-danger", label: "Заказать звонок",
bg: "#dc2626", cssClass: ".bb-btn-pill",
text: "#fff", bg: "#e9e4d4",
description: "Деструктивные и необратимые действия. Красный фон.", border: "#d5cfbd",
useCase: "Удалить · Отменить запись", textColor: "#333",
radius: "25px",
shadow: "нет",
where: "Модальные триггеры, мягкий CTA",
example: "«Заказать звонок»",
note: "Кремовый фон + pill-форма — мягкий стиль. Используется для открытия модальных окон.",
}, },
]; ];
const LLM_BUTTONS_TEXT = `КНОПКИ — LLM-спецификация const LLM_BUTTONS_TEXT = `КНОПКИ — LLM-спецификация (с реального сайта oclinica.ru)
Версия: v1.0 · /components/buttons Версия: v2.0 · /components/buttons
Источник CSS: perm.oclinica.ru/.../style.css
ВАРИАНТЫ ВАРИАНТЫ (реальный сайт)
Вариант | CSS класс | Фон | Текст | Граница | Применение Вариант | CSS класс | Фон | Текст | Граница | Radius | Shadow | Применение
primary | .bb-btn-primary | #5b7b87 | #fff | #5b7b87 | Главный CTA: «Записаться», «Подтвердить» primary | .bb-btn-primary | #FFA39C | #fff | #FF847B | 7px | да | Форм-сабмит «Запишите меня!»
secondary | .bb-btn-secondary | прозрачный | #5b7b87 | #5b7b87 | Второстепенное: «Подробнее», «Редактировать» outline | .bb-btn-outline | #fff | #BF9975 | #BF9975 | 7px | нет | Хедер «Записаться на прием», ссылки-кнопки
ghost | .bb-btn-ghost | прозрачный | #5b7b87 | нет | Третичное: «Отмена», «Назад» teal | .bb-btn-teal | #60959c | #fff | нет | 7px | нет | Звонок «Позвонить»
danger | .bb-btn-danger | #dc2626 | #fff | #dc2626 | Деструктивное: «Удалить», «Отменить запись» pill | .bb-btn-pill | #e9e4d4 | #333 | #d5cfbd | 25px | нет | Callback «Заказать звонок»
РАЗМЕРЫ CSS С САЙТА (точные значения)
Размер | CSS класс | padding | font-size | border-radius | Применение /* форм-кнопка «Запишите меня!» */
sm | .bb-btn-sm | 5px 12px | 13px | 6px | Компактные интерфейсы, таблицы button { background:#FFA39C; color:white; font-weight:bold; border:solid 1px #FF847B;
md | .bb-btn-md | 8px 18px | 14px | 8px | Стандарт (по умолчанию) height:42px; font-size:18px; box-shadow:0px 0px 5px rgba(0,0,0,0.5),0px 4px 5px rgba(0,0,0,0.3); }
lg | .bb-btn-lg | 12px 26px | 16px | 10px | Главные CTA на Hero-блоках
СОСТОЯНИЯ /* appointment — «Записаться на прием» */
default — без изменений .appointment { background:#FFF; border:#BF9975 solid 1px; color:#BF9975;
hover — filter: brightness(0.9) font-size:14px; line-height:38px; padding:3px 12px; border-radius:7px; }
active — filter: brightness(0.82)
loading — spinner (animation: bb-spin 0.65s linear infinite) + opacity: 0.5 + disabled
disabled — opacity: 0.5, cursor: not-allowed
CSS BASE (globals.css) /* show-phone — «Позвонить» */
.bb-btn { font-family: Fira Sans; font-weight: 500; display: inline-flex; align-items: center; gap: 7px; transition: filter 0.15s; } .show-phone { background:rgb(96,149,156); color:#fff; border-radius:7px;
.bb-btn:hover:not(:disabled) { filter: brightness(0.9); } font-size:14px; line-height:38px; padding:3px 12px; }
.bb-btn:active:not(:disabled) { filter: brightness(0.82); }
.bb-btn:disabled { cursor: not-allowed; opacity: 0.5; } /* callback — «Заказать звонок» */
.bb-btn:focus-visible { outline: 2px solid #7ecfca; outline-offset: 2px; } a.callback_url { background:#e9e4d4; border:#d5cfbd solid 1px; color:#000;
border-radius:25px; font-size:16px; padding:6px 18px; }
РАЗМЕРЫ (брендбук-компонент)
Размер | CSS класс | padding | font-size | Применение
sm | .bb-btn-sm | 4px 11px | 13px | Компактные контексты
md | .bb-btn-md | 8px 16px | 14px | Стандарт (appointment, teal, pill)
lg | .bb-btn-lg | 10px 24px | 18px + bold | Форм-сабмит (соответствует реальному сайту)
ПРАВИЛА ПРИМЕНЕНИЯ ПРАВИЛА ПРИМЕНЕНИЯ
Не более одной primary-кнопки на видимый экран в контексте одной задачи primary (коралловый) — только для главного CTA в форме записи
Текст кнопки — глагол или чёткий призыв: «Записаться», «Узнать цену» outline (бежевый) — хедер, навигация, ссылки-кнопки на странице
Primary → главное действие (форма записи, подтверждение) teal (бирюзовый) — контактные действия (звонок, направление)
Secondary → второстепенное (подробнее, редактировать) pill (кремовый) — открытие модальных окон, мягкий callback
Ghost → отмена, навигационная ссылка без акцента Не более одного primary на форму
✓ Danger → только деструктивные действия (удалить, отменить запись) ✕ Не менять цвета вне фирменной палитры сайта
Не менять цвета произвольно — только варианты из фирменной палитры Primary — не для навигационных ссылок
✕ Не добавлять тени к кнопкам ✕ Не накладывать тень на outline/teal/pill`.trim();
✕ Не использовать Danger для нейтральных действий`.trim();
export default function ButtonsPage() { export default function ButtonsPage() {
const codeHtml = `<!-- HTML — базовые классы из globals.css --> const codeHtml = `<!-- Primary — форм-кнопка «Запишите меня!» -->
<button class="bb-btn bb-btn-md bb-btn-primary">Записаться</button> <button class="bb-btn bb-btn-lg bb-btn-primary">Запишите меня!</button>
<button class="bb-btn bb-btn-md bb-btn-secondary">Узнать подробнее</button>
<button class="bb-btn bb-btn-md bb-btn-ghost">Отмена</button>
<button class="bb-btn bb-btn-md bb-btn-danger">Удалить</button>
<!-- Размеры --> <!-- Outline — appointment «Записаться на прием» -->
<button class="bb-btn bb-btn-sm bb-btn-primary">Маленькая</button> <a class="bb-btn bb-btn-md bb-btn-outline" href="#form">Записаться на прием</a>
<button class="bb-btn bb-btn-md bb-btn-primary">Средняя</button>
<button class="bb-btn bb-btn-lg bb-btn-primary">Большая</button>`; <!-- Teal — «Позвонить» -->
<a class="bb-btn bb-btn-md bb-btn-teal" href="tel:+73422250662">Позвонить</a>
<!-- Pill — «Заказать звонок» -->
<a class="bb-btn bb-btn-md bb-btn-pill" href="#callback">Заказать звонок</a>`;
const codeReact = `import { Button } from "@/components/ui/Button"; const codeReact = `import { Button } from "@/components/ui/Button";
// Варианты // Форм-кнопка (главный CTA)
<Button variant="primary">Записаться</Button> <Button variant="primary" size="lg">Запишите меня!</Button>
<Button variant="secondary">Узнать подробнее</Button>
<Button variant="ghost">Отмена</Button>
<Button variant="danger">Удалить</Button>
// Размеры // Запись из хедера / навигации
<Button size="sm">Маленькая</Button> <Button variant="outline" size="md">Записаться на прием</Button>
<Button size="md">Средняя</Button> {/* по умолчанию */}
<Button size="lg">Большая</Button>
// Состояния // Звонок
<Button loading>Загрузка...</Button> <Button variant="teal" size="md">Позвонить</Button>
<Button disabled>Недоступно</Button>`;
// Заказать звонок (открывает модал)
<Button variant="pill" size="md">Заказать звонок</Button>
// С loading-состоянием
<Button variant="primary" size="lg" loading>Отправляем...</Button>`;
const codeSiteExact = `/* Точный CSS с сайта oclinica.ru (style.css) */
/* Форм-кнопка — кнопка отправки форм записи */
#block-entityform-block-feedback button,
#block-entityform-block-lor-form button {
background: #FFA39C;
color: white;
font-weight: bold;
border: solid 1px #FF847B;
width: 300px;
height: 42px;
font-size: 18px;
box-shadow: 0px 0px 5px rgba(0,0,0,0.5), 0px 4px 5px rgba(0,0,0,0.3);
}
/* Кнопка «Записаться на прием» в хедере */
#block-block-15 .appointment {
background: #FFF;
border: #BF9975 solid 1px;
color: #BF9975;
font-size: 14px;
line-height: 38px;
padding: 3px 12px;
border-radius: 7px;
}
/* Кнопка «Позвонить» */
.show-phone {
background: rgb(96, 149, 156); /* #60959c */
color: #fff;
border-radius: 7px;
font-size: 14px;
line-height: 38px;
padding: 3px 12px;
}
/* Кнопка «Заказать звонок» */
a.callback_url {
background: #e9e4d4;
border: #d5cfbd solid 1px;
color: #000;
border-radius: 25px;
font-size: 16px;
padding: 6px 18px;
}`;
return ( return (
<div className="max-w-4xl mx-auto px-8 py-10"> <div className="max-w-4xl mx-auto px-8 py-10">
@@ -158,46 +228,89 @@ export default function ButtonsPage() {
Кнопки Кнопки
</h1> </h1>
<p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}> <p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}>
Все варианты кнопок, применяемых на сайте клиники. Кнопки скопированы с реального сайта{" "}
Кнопки основной элемент призыва к действию в интерфейсе. <span className="font-mono text-sm" style={{ color: "var(--bb-text)" }}>
oclinica.ru
</span>
. Цвета, размеры и тени взяты напрямую из CSS темы{" "}
<span className="font-mono text-sm" style={{ color: "var(--bb-text)" }}>
clinic_bootstrap_mobile/css/style.css
</span>
.
</p> </p>
<div
className="mt-4 px-4 py-3 rounded-lg border text-sm flex items-center gap-2"
style={{ borderColor: "#e0f5f4", background: "#f8fffe", color: "var(--bb-text-muted)" }}
>
<span style={{ color: "var(--brand-053m)", fontWeight: 600 }}>Источник</span>
<span>
CSS сайта проанализирован 2026-03-22 4 типа кнопок с реальными значениями.
</span>
</div>
</div> </div>
{/* 1. Варианты */} {/* 1. Варианты */}
<Section <Section
id="variants" id="variants"
title="Варианты" title="Варианты"
subtitle="Четыре визуальных типа кнопок для разных контекстов." subtitle="Четыре типа кнопок с реального сайта клиники."
> >
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
{VARIANT_INFO.map(({ variant, label, description, useCase }) => ( {VARIANTS.map(({ variant, name, label, where, example, note, bg, border, textColor, radius, shadow }) => (
<div <div
key={variant} key={variant}
className="rounded-xl border p-5 flex flex-col gap-4" className="rounded-xl border p-5 flex flex-col gap-4"
style={{ borderColor: "var(--bb-border)" }} style={{ borderColor: "var(--bb-border)" }}
> >
{/* Превью */}
<div <div
className="flex items-center justify-center py-6 rounded-lg" className="flex items-center justify-center py-6 rounded-lg"
style={{ background: "var(--bb-sidebar-bg)" }} style={{ background: "var(--bb-sidebar-bg)" }}
> >
<Button variant={variant}>{label}</Button> <Button variant={variant} size="md">
</div>
<div>
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
{label} {label}
</Button>
</div>
{/* Инфо */}
<div>
<p className="font-semibold text-sm mb-1" style={{ color: "var(--bb-text)" }}>
{name}
</p> </p>
<p <p className="text-xs mb-2 leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>
className="text-xs mb-3 leading-relaxed" {note}
style={{ color: "var(--bb-text-muted)" }}
>
{description}
</p> </p>
<span <div className="flex flex-wrap gap-1.5 mb-2">
className="inline-block text-[11px] px-2 py-0.5 rounded" {[
style={{ background: "#e0f5f4", color: "var(--brand-073m)" }} { k: "bg", v: bg },
{ k: "text", v: textColor },
{ k: "border", v: border },
{ k: "radius", v: radius },
...(shadow === "да" ? [{ k: "shadow", v: "да" }] : []),
].map(({ k, v }) => (
<span
key={k}
className="text-[10px] font-mono px-1.5 py-0.5 rounded"
style={{ background: "#f3f4f6", color: "var(--bb-text-muted)" }}
>
{k}: {v}
</span>
))}
</div>
<div
className="rounded p-2.5 text-xs"
style={{ background: "#f8f9fa", color: "var(--bb-text-muted)" }}
> >
{useCase} <span className="font-medium" style={{ color: "var(--bb-text)" }}>
</span> Где:
</span>{" "}
{where}
<br />
<span className="font-medium" style={{ color: "var(--bb-text)" }}>
Пример:
</span>{" "}
{example}
</div>
</div> </div>
</div> </div>
))} ))}
@@ -208,7 +321,7 @@ export default function ButtonsPage() {
<Section <Section
id="sizes" id="sizes"
title="Размеры" title="Размеры"
subtitle="Три размера для разных уровней иерархии интерфейса." subtitle="Три размера для разных контекстов. lg соответствует форм-кнопке на реальном сайте (18px, bold)."
> >
<div <div
className="rounded-xl border overflow-hidden" className="rounded-xl border overflow-hidden"
@@ -219,20 +332,20 @@ export default function ButtonsPage() {
{ {
size: "sm" as const, size: "sm" as const,
label: "Small", label: "Small",
hint: "padding: 5px 12px · font: 13px · radius: 6px", hint: "4px 11px · 13px",
use: "Компактные интерфейсы, действия в таблицах", use: "Компактные контексты, таблицы",
}, },
{ {
size: "md" as const, size: "md" as const,
label: "Medium", label: "Medium",
hint: "padding: 8px 18px · font: 14px · radius: 8px", hint: "8px 16px · 14px",
use: "Стандартный размер (по умолчанию)", use: "Appointment, Teal, Pill (соответствует сайту)",
}, },
{ {
size: "lg" as const, size: "lg" as const,
label: "Large", label: "Large",
hint: "padding: 12px 26px · font: 16px · radius: 10px", hint: "10px 24px · 18px bold",
use: "Главные CTA на Hero-блоках", use: "Primary форм-кнопка (соответствует сайту)",
}, },
] as const ] as const
).map(({ size, label, hint, use }, i) => ( ).map(({ size, label, hint, use }, i) => (
@@ -241,7 +354,7 @@ export default function ButtonsPage() {
className="flex items-center gap-6 px-5 py-4" className="flex items-center gap-6 px-5 py-4"
style={{ borderTop: i > 0 ? "1px solid var(--bb-border)" : undefined }} style={{ borderTop: i > 0 ? "1px solid var(--bb-border)" : undefined }}
> >
<div className="w-36 shrink-0"> <div className="w-40 shrink-0">
<Button variant="primary" size={size}> <Button variant="primary" size={size}>
Записаться Записаться
</Button> </Button>
@@ -251,12 +364,12 @@ export default function ButtonsPage() {
{label} {label}
</p> </p>
<p className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}> <p className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>
{hint} padding: {hint.split("·")[0].trim()} · font-size: {hint.split("·")[1].trim()}
</p> </p>
</div> </div>
<p <p
className="text-xs hidden lg:block" className="text-xs hidden lg:block"
style={{ color: "var(--bb-text-muted)", maxWidth: 200 }} style={{ color: "var(--bb-text-muted)", maxWidth: 220 }}
> >
{use} {use}
</p> </p>
@@ -269,49 +382,53 @@ export default function ButtonsPage() {
<Section <Section
id="states" id="states"
title="Состояния" title="Состояния"
subtitle="Поведение кнопки при разных условиях взаимодействия." subtitle="Базовые состояния кнопки. На реальном сайте hover/transition не определены в CSS."
> >
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{( {(
[ [
{ {
label: "Default", label: "Default",
node: <Button variant="primary">Записаться</Button>, node: <Button variant="primary" size="lg">Записаться</Button>,
hint: "Стандартное состояние", hint: "Стандартное состояние",
}, },
{ {
label: "Hover", label: "Hover",
node: ( node: (
<Button variant="primary" style={{ filter: "brightness(0.9)" }}> <Button
variant="primary"
size="lg"
style={{ filter: "brightness(0.93)" }}
>
Записаться Записаться
</Button> </Button>
), ),
hint: "filter: brightness(0.9) при наведении курсора", hint: "filter: brightness(0.93)",
}, },
{ {
label: "Loading", label: "Loading",
node: <Button variant="primary" loading>Загрузка...</Button>, node: <Button variant="primary" size="lg" loading>Отправка...</Button>,
hint: "Спиннер + opacity: 0.5 + кнопка заблокирована", hint: "Спиннер + blocked",
}, },
{ {
label: "Disabled", label: "Disabled",
node: <Button variant="primary" disabled>Недоступно</Button>, node: <Button variant="primary" size="lg" disabled>Записаться</Button>,
hint: "opacity: 0.5, cursor: not-allowed", hint: "opacity: 0.5",
}, },
] as const ] as const
).map(({ label, node, hint }) => ( ).map(({ label, node, hint }) => (
<div <div
key={label} key={label}
className="rounded-xl border p-5" className="rounded-xl border p-4"
style={{ borderColor: "var(--bb-border)" }} style={{ borderColor: "var(--bb-border)" }}
> >
<div <div
className="flex items-center justify-center py-6 mb-3 rounded-lg" className="flex items-center justify-center py-4 mb-3 rounded-lg"
style={{ background: "var(--bb-sidebar-bg)" }} style={{ background: "var(--bb-sidebar-bg)" }}
> >
{node} {node}
</div> </div>
<p className="text-sm font-medium mb-1" style={{ color: "var(--bb-text)" }}> <p className="text-sm font-medium mb-0.5" style={{ color: "var(--bb-text)" }}>
{label} {label}
</p> </p>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}> <p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
@@ -322,51 +439,81 @@ export default function ButtonsPage() {
</div> </div>
</Section> </Section>
{/* 4. Все варианты вместе */} {/* 4. Контекст применения */}
<Section <Section
id="all" id="context"
title="Все варианты и размеры" title="Где применяется"
subtitle="Сводная таблица — визуальное сравнение." subtitle="Таблица: тип кнопки → реальное использование на сайте."
> >
<div <div
className="rounded-xl border overflow-hidden" className="rounded-xl border overflow-hidden"
style={{ borderColor: "var(--bb-border)" }} style={{ borderColor: "var(--bb-border)" }}
> >
<div <table className="w-full text-sm">
className="px-5 py-3 text-xs font-medium border-b" <thead>
style={{ <tr style={{ background: "var(--bb-sidebar-bg)" }}>
color: "var(--bb-text-muted)", {["Вариант", "Цвет фона", "Реальный класс/контекст", "Текст кнопки на сайте"].map((h) => (
background: "var(--bb-sidebar-bg)", <th
borderColor: "var(--bb-border)", key={h}
}} className="text-left px-4 py-3 font-medium text-xs"
> style={{ color: "var(--bb-text-muted)" }}
Вариант / Размер >
</div> {h}
{VARIANT_INFO.map(({ variant, label }, vi) => ( </th>
<div ))}
key={variant} </tr>
className="flex items-center gap-5 px-5 py-3" </thead>
style={{ borderTop: vi > 0 ? "1px solid var(--bb-border)" : undefined }} <tbody>
> {[
<span {
className="w-20 text-xs font-mono shrink-0" v: <Button variant="primary" size="sm">Primary</Button>,
style={{ color: "var(--bb-text-muted)" }} bg: "#FFA39C",
> ctx: "button в entityform-блоках форм записи",
{label} text: "«Запишите меня!»",
</span> },
<div className="flex items-center gap-3 flex-wrap"> {
<Button variant={variant} size="sm"> v: <Button variant="outline" size="sm">Outline</Button>,
Маленькая bg: "#fff / рамка #BF9975",
</Button> ctx: ".appointment в хедере (block-block-15, 30, 32, 24)",
<Button variant={variant} size="md"> text: "«Записаться на прием»",
Средняя },
</Button> {
<Button variant={variant} size="lg"> v: <Button variant="teal" size="sm">Teal</Button>,
Большая bg: "#60959c",
</Button> ctx: ".show-phone (block-block-4, 15)",
</div> text: "«Позвонить»",
</div> },
))} {
v: <Button variant="pill" size="sm">Pill</Button>,
bg: "#e9e4d4",
ctx: "a.callback_url (modal trigger)",
text: "«Заказать звонок»",
},
].map(({ v, bg, ctx, text }, i) => (
<tr
key={i}
style={{ borderTop: "1px solid var(--bb-border)" }}
>
<td className="px-4 py-3">{v}</td>
<td
className="px-4 py-3 font-mono text-xs"
style={{ color: "var(--bb-text-muted)" }}
>
{bg}
</td>
<td
className="px-4 py-3 font-mono text-xs"
style={{ color: "var(--bb-text-muted)" }}
>
{ctx}
</td>
<td className="px-4 py-3 text-xs" style={{ color: "var(--bb-text)" }}>
{text}
</td>
</tr>
))}
</tbody>
</table>
</div> </div>
</Section> </Section>
@@ -374,58 +521,50 @@ export default function ButtonsPage() {
<Section <Section
id="code" id="code"
title="Примеры кода" title="Примеры кода"
subtitle="Скопируйте HTML или JSX для использования в проекте." subtitle="HTML-классы из globals.css, JSX-компонент, и точный CSS с сайта."
> >
<div className="space-y-4"> <div className="space-y-4">
<CodeCopy lang="HTML (CSS-классы из globals.css)" code={codeHtml} /> <CodeCopy lang="HTML (CSS-классы brandbook)" code={codeHtml} />
<CodeCopy lang="JSX (React / Next.js)" code={codeReact} /> <CodeCopy lang="JSX (React / Next.js)" code={codeReact} />
<CodeCopy lang="CSS — точно с сайта oclinica.ru" code={codeSiteExact} />
</div> </div>
</Section> </Section>
{/* LLM-блок */} {/* LLM-блок */}
<LlmBlock path="/components/buttons" version="v1.0" specText={LLM_BUTTONS_TEXT}> <LlmBlock path="/components/buttons" version="v2.0" specText={LLM_BUTTONS_TEXT}>
<LlmSection title="Варианты" /> <LlmSection title="Варианты (реальный сайт oclinica.ru)" />
<LlmTable <LlmTable
headers={["Вариант", "CSS класс", "Фон", "Текст", "Применение"]} headers={["Вариант", "CSS класс", "Фон", "Текст", "Border", "Radius", "Применение"]}
rows={VARIANT_INFO.map((v) => [ rows={VARIANTS.map((v) => [
v.variant, v.variant,
v.cssClass, v.cssClass,
v.bg, v.bg,
v.text, v.textColor,
v.useCase, v.border,
v.radius,
v.where,
])} ])}
/> />
<LlmSection title="Размеры" /> <LlmSection title="Размеры (брендбук-компонент)" />
<LlmTable <LlmTable
headers={["Размер", "CSS класс", "padding", "font-size", "radius", "Применение"]} headers={["Размер", "padding", "font-size", "Применение"]}
rows={[ rows={[
["sm", ".bb-btn-sm", "5px 12px", "13px", "6px", "Компактные интерфейсы, таблицы"], ["sm", "4px 11px", "13px", "Компактные контексты"],
["md", ".bb-btn-md", "8px 18px", "14px", "8px", "Стандарт (по умолчанию)"], ["md", "8px 16px", "14px", "Стандарт (outline, teal, pill с сайта)"],
["lg", ".bb-btn-lg", "12px 26px", "16px", "10px", "Hero CTA"], ["lg", "10px 24px", "18px bold", "Primary форм-кнопка (соответствует сайту)"],
]}
/>
<LlmSection title="Состояния" />
<LlmTable
headers={["Состояние", "CSS / Поведение"]}
rows={[
["default", "без изменений"],
["hover", "filter: brightness(0.9)"],
["active", "filter: brightness(0.82)"],
["loading", "spinner bb-spin + opacity: 0.5 + disabled=true"],
["disabled", "opacity: 0.5 + cursor: not-allowed"],
]} ]}
/> />
<LlmSection title="Правила применения" /> <LlmSection title="Правила применения" />
<LlmRules <LlmRules
rules={[ rules={[
{ ok: true, text: "Не более одной primary-кнопки на один экран в контексте задачи" }, { ok: true, text: "primary (коралловый) — только для submit в формах записи" },
{ ok: true, text: "Текст — глагол или призыв: «Записаться», «Узнать цену»" }, { ok: true, text: "outline (бежевый) — хедер, навигация, второстепенные ссылки" },
{ ok: true, text: "Primary → главное действие формы или подтверждения" }, { ok: true, text: "teal (бирюзовый) — телефонные и контактные действия" },
{ ok: true, text: "Ghost → отмена и навигационные ссылки без акцента" }, { ok: true, text: "pill (кремовый) — открытие модальных окон / callback" },
{ ok: true, text: "Danger → только деструктивные действия" }, { ok: true, text: "Не более одного primary на форму" },
{ ok: false, text: "Не менять цвета произвольно вне фирменной палитры" }, { ok: false, text: "Не менять цвета вне указанной палитры сайта" },
{ ok: false, text: "Не добавлять тени к кнопкам" }, { ok: false, text: "Primary — не для навигационных ссылок" },
{ ok: false, text: "Не использовать Danger для нейтральных действий" }, { ok: false, text: "Не накладывать тень на outline, teal, pill" },
]} ]}
/> />
</LlmBlock> </LlmBlock>
+39 -7
View File
@@ -68,14 +68,46 @@ body {
.bb-btn:disabled { cursor: not-allowed; opacity: 0.5; } .bb-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.bb-btn:focus-visible { outline: 2px solid var(--brand-053m); outline-offset: 2px; } .bb-btn:focus-visible { outline: 2px solid var(--brand-053m); outline-offset: 2px; }
.bb-btn-sm { font-size: 13px; padding: 5px 12px; border-radius: 6px; border: 1.5px solid transparent; } /* Размеры — только padding и font-size, radius задаётся вариантом */
.bb-btn-md { font-size: 14px; padding: 8px 18px; border-radius: 8px; border: 1.5px solid transparent; } .bb-btn-sm { font-size: 13px; padding: 4px 11px; border: 1px solid transparent; }
.bb-btn-lg { font-size: 16px; padding: 12px 26px; border-radius: 10px; border: 1.5px solid transparent; } .bb-btn-md { font-size: 14px; padding: 8px 16px; border: 1px solid transparent; }
.bb-btn-lg { font-size: 18px; padding: 10px 24px; border: 1px solid transparent; font-weight: bold; }
.bb-btn-primary { background: var(--brand-073m); color: #fff; border-color: var(--brand-073m); } /* Варианты — цвета и радиус по реальному сайту oclinica.ru */
.bb-btn-secondary { background: transparent; color: var(--brand-073m); border-color: var(--brand-073m); }
.bb-btn-ghost { background: transparent; color: var(--brand-073m); border-color: transparent; } /* primary — коралловая форм-кнопка «Запишите меня!» */
.bb-btn-danger { background: #dc2626; color: #fff; border-color: #dc2626; } .bb-btn-primary {
background: #FFA39C;
color: #fff;
border-color: #FF847B;
border-radius: 7px;
font-weight: bold;
box-shadow: 0px 0px 5px rgba(0,0,0,0.4), 0px 3px 5px rgba(0,0,0,0.25);
}
/* outline — белая с бежевой рамкой «Записаться на прием» */
.bb-btn-outline {
background: #fff;
color: #BF9975;
border-color: #BF9975;
border-radius: 7px;
}
/* teal — бирюзовая «Позвонить» */
.bb-btn-teal {
background: #60959c;
color: #fff;
border-color: transparent;
border-radius: 7px;
}
/* pill — кремовая таблетка «Заказать звонок» */
.bb-btn-pill {
background: #e9e4d4;
color: #333;
border-color: #d5cfbd;
border-radius: 25px;
}
/* ─── Форм-контролы (Sprint 3) ───────────────────────────────── */ /* ─── Форм-контролы (Sprint 3) ───────────────────────────────── */
.bb-input, .bb-input,
+1 -1
View File
@@ -2,7 +2,7 @@
import { ButtonHTMLAttributes, forwardRef } from "react"; import { ButtonHTMLAttributes, forwardRef } from "react";
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"; export type ButtonVariant = "primary" | "outline" | "teal" | "pill";
export type ButtonSize = "sm" | "md" | "lg"; export type ButtonSize = "sm" | "md" | "lg";
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {