Browse Source

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>
sprint/3
AR 15 M4 1 week ago
parent
commit
c1731615ab
  1. 519
      apps/web/app/components/buttons/page.tsx
  2. 48
      apps/web/app/globals.css
  3. 2
      apps/web/components/ui/Button.tsx

519
apps/web/app/components/buttons/page.tsx

@ -35,114 +35,184 @@ function Section({
);
}
const VARIANT_INFO = [
const VARIANTS = [
{
variant: "primary" as const,
label: "Primary",
name: "Primary",
label: "Запишитесь к нам",
cssClass: ".bb-btn-primary",
bg: "#5b7b87",
text: "#fff",
description: "Основная кнопка призыва к действию. Тёмный бирюзово-серый фон, белый текст.",
useCase: "Записаться · Подтвердить",
bg: "#FFA39C",
border: "#FF847B",
textColor: "#fff",
radius: "7px",
shadow: "да",
where: "Кнопка отправки форм записи",
example: "«Запишите меня!»",
note: "Коралловый — самый заметный акцент на странице. Всегда один в форме.",
},
{
variant: "secondary" as const,
label: "Secondary",
cssClass: ".bb-btn-secondary",
bg: "прозрачный",
text: "#5b7b87",
description: "Второстепенное действие. Контурная кнопка с фирменным цветом.",
useCase: "Узнать подробнее · Редактировать",
variant: "outline" as const,
name: "Outline",
label: "Записаться на приём",
cssClass: ".bb-btn-outline",
bg: "#fff",
border: "#BF9975",
textColor: "#BF9975",
radius: "7px",
shadow: "нет",
where: "Хедер, навигация, ссылки-кнопки",
example: "«Записаться на прием», «Все новости»",
note: "Бежевая рамка — ненавязчивый вторичный CTA. Не конкурирует с основной формой.",
},
{
variant: "ghost" as const,
label: "Ghost",
cssClass: ".bb-btn-ghost",
bg: "прозрачный",
text: "#5b7b87",
description: "Третичное действие. Без фона и видимой рамки, текстовый акцент.",
useCase: "Отмена · Назад · Ещё...",
variant: "teal" as const,
name: "Teal",
label: "Позвонить",
cssClass: ".bb-btn-teal",
bg: "#60959c",
border: "прозрачный",
textColor: "#fff",
radius: "7px",
shadow: "нет",
where: "Контактные действия — звонок",
example: "«Позвонить»",
note: "Серо-бирюзовый — цвет из реального CSS сайта. Близок к Oracal 066M.",
},
{
variant: "danger" as const,
label: "Danger",
cssClass: ".bb-btn-danger",
bg: "#dc2626",
text: "#fff",
description: "Деструктивные и необратимые действия. Красный фон.",
useCase: "Удалить · Отменить запись",
variant: "pill" as const,
name: "Pill",
label: "Заказать звонок",
cssClass: ".bb-btn-pill",
bg: "#e9e4d4",
border: "#d5cfbd",
textColor: "#333",
radius: "25px",
shadow: "нет",
where: "Модальные триггеры, мягкий CTA",
example: "«Заказать звонок»",
note: "Кремовый фон + pill-форма — мягкий стиль. Используется для открытия модальных окон.",
},
];
const LLM_BUTTONS_TEXT = `КНОПКИ — LLM-спецификация
Версия: v1.0 · /components/buttons
ВАРИАНТЫ
Вариант | CSS класс | Фон | Текст | Граница | Применение
primary | .bb-btn-primary | #5b7b87 | #fff | #5b7b87 | Главный CTA: «Записаться», «Подтвердить»
secondary | .bb-btn-secondary | прозрачный | #5b7b87 | #5b7b87 | Второстепенное: «Подробнее», «Редактировать»
ghost | .bb-btn-ghost | прозрачный | #5b7b87 | нет | Третичное: «Отмена», «Назад»
danger | .bb-btn-danger | #dc2626 | #fff | #dc2626 | Деструктивное: «Удалить», «Отменить запись»
РАЗМЕРЫ
Размер | CSS класс | padding | font-size | border-radius | Применение
sm | .bb-btn-sm | 5px 12px | 13px | 6px | Компактные интерфейсы, таблицы
md | .bb-btn-md | 8px 18px | 14px | 8px | Стандарт (по умолчанию)
lg | .bb-btn-lg | 12px 26px | 16px | 10px | Главные CTA на Hero-блоках
СОСТОЯНИЯ
default без изменений
hover filter: brightness(0.9)
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)
.bb-btn { font-family: Fira Sans; font-weight: 500; display: inline-flex; align-items: center; gap: 7px; transition: filter 0.15s; }
.bb-btn:hover:not(:disabled) { filter: brightness(0.9); }
.bb-btn:active:not(:disabled) { filter: brightness(0.82); }
.bb-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.bb-btn:focus-visible { outline: 2px solid #7ecfca; outline-offset: 2px; }
const LLM_BUTTONS_TEXT = `КНОПКИ — LLM-спецификация (с реального сайта oclinica.ru)
Версия: v2.0 · /components/buttons
Источник CSS: perm.oclinica.ru/.../style.css
ВАРИАНТЫ (реальный сайт)
Вариант | CSS класс | Фон | Текст | Граница | Radius | Shadow | Применение
primary | .bb-btn-primary | #FFA39C | #fff | #FF847B | 7px | да | Форм-сабмит «Запишите меня!»
outline | .bb-btn-outline | #fff | #BF9975 | #BF9975 | 7px | нет | Хедер «Записаться на прием», ссылки-кнопки
teal | .bb-btn-teal | #60959c | #fff | нет | 7px | нет | Звонок «Позвонить»
pill | .bb-btn-pill | #e9e4d4 | #333 | #d5cfbd | 25px | нет | Callback «Заказать звонок»
CSS С САЙТА (точные значения)
/* форм-кнопка «Запишите меня!» */
button { background:#FFA39C; color:white; font-weight:bold; border:solid 1px #FF847B;
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); }
/* appointment — «Записаться на прием» */
.appointment { background:#FFF; border:#BF9975 solid 1px; color:#BF9975;
font-size:14px; line-height:38px; padding:3px 12px; border-radius:7px; }
/* show-phone — «Позвонить» */
.show-phone { background:rgb(96,149,156); color:#fff; border-radius:7px;
font-size:14px; line-height:38px; padding:3px 12px; }
/* callback — «Заказать звонок» */
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 главное действие (форма записи, подтверждение)
Secondary второстепенное (подробнее, редактировать)
Ghost отмена, навигационная ссылка без акцента
Danger только деструктивные действия (удалить, отменить запись)
Не менять цвета произвольно только варианты из фирменной палитры
Не добавлять тени к кнопкам
Не использовать Danger для нейтральных действий`.trim();
primary (коралловый) только для главного CTA в форме записи
outline (бежевый) хедер, навигация, ссылки-кнопки на странице
teal (бирюзовый) контактные действия (звонок, направление)
pill (кремовый) открытие модальных окон, мягкий callback
Не более одного primary на форму
Не менять цвета вне фирменной палитры сайта
Primary не для навигационных ссылок
Не накладывать тень на outline/teal/pill`.trim();
export default function ButtonsPage() {
const codeHtml = `<!-- HTML — базовые классы из globals.css -->
<button class="bb-btn bb-btn-md 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>
const codeHtml = `<!-- Primary — форм-кнопка «Запишите меня!» -->
<button class="bb-btn bb-btn-lg bb-btn-primary">Запишите меня!</button>
<!-- Outline appointment «Записаться на прием» -->
<a class="bb-btn bb-btn-md bb-btn-outline" href="#form">Записаться на прием</a>
<!-- Размеры -->
<button class="bb-btn bb-btn-sm bb-btn-primary">Маленькая</button>
<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";
// Варианты
<Button variant="primary">Записаться</Button>
<Button variant="secondary">Узнать подробнее</Button>
<Button variant="ghost">Отмена</Button>
<Button variant="danger">Удалить</Button>
// Форм-кнопка (главный CTA)
<Button variant="primary" size="lg">Запишите меня!</Button>
// Запись из хедера / навигации
<Button variant="outline" size="md">Записаться на прием</Button>
// Размеры
<Button size="sm">Маленькая</Button>
<Button size="md">Средняя</Button> {/* по умолчанию */}
<Button size="lg">Большая</Button>
// Звонок
<Button variant="teal" size="md">Позвонить</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;
}
// Состояния
<Button loading>Загрузка...</Button>
<Button disabled>Недоступно</Button>`;
/* Кнопка «Заказать звонок» */
a.callback_url {
background: #e9e4d4;
border: #d5cfbd solid 1px;
color: #000;
border-radius: 25px;
font-size: 16px;
padding: 6px 18px;
}`;
return (
<div className="max-w-4xl mx-auto px-8 py-10">
@ -158,46 +228,89 @@ export default function ButtonsPage() {
Кнопки
</h1>
<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>
<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>
{/* 1. Варианты */}
<Section
id="variants"
title="Варианты"
subtitle="Четыре визуальных типа кнопок для разных контекстов."
subtitle="Четыре типа кнопок с реального сайта клиники."
>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
{VARIANT_INFO.map(({ variant, label, description, useCase }) => (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
{VARIANTS.map(({ variant, name, label, where, example, note, bg, border, textColor, radius, shadow }) => (
<div
key={variant}
className="rounded-xl border p-5 flex flex-col gap-4"
style={{ borderColor: "var(--bb-border)" }}
>
{/* Превью */}
<div
className="flex items-center justify-center py-6 rounded-lg"
style={{ background: "var(--bb-sidebar-bg)" }}
>
<Button variant={variant}>{label}</Button>
<Button variant={variant} size="md">
{label}
</Button>
</div>
{/* Инфо */}
<div>
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
{label}
<p className="font-semibold text-sm mb-1" style={{ color: "var(--bb-text)" }}>
{name}
</p>
<p
className="text-xs mb-3 leading-relaxed"
style={{ color: "var(--bb-text-muted)" }}
>
{description}
<p className="text-xs mb-2 leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>
{note}
</p>
<div className="flex flex-wrap gap-1.5 mb-2">
{[
{ k: "bg", v: bg },
{ k: "text", v: textColor },
{ k: "border", v: border },
{ k: "radius", v: radius },
...(shadow === "да" ? [{ k: "shadow", v: "да" }] : []),
].map(({ k, v }) => (
<span
className="inline-block text-[11px] px-2 py-0.5 rounded"
style={{ background: "#e0f5f4", color: "var(--brand-073m)" }}
key={k}
className="text-[10px] font-mono px-1.5 py-0.5 rounded"
style={{ background: "#f3f4f6", color: "var(--bb-text-muted)" }}
>
{useCase}
{k}: {v}
</span>
))}
</div>
<div
className="rounded p-2.5 text-xs"
style={{ background: "#f8f9fa", color: "var(--bb-text-muted)" }}
>
<span className="font-medium" style={{ color: "var(--bb-text)" }}>
Где:
</span>{" "}
{where}
<br />
<span className="font-medium" style={{ color: "var(--bb-text)" }}>
Пример:
</span>{" "}
{example}
</div>
</div>
</div>
))}
@ -208,7 +321,7 @@ export default function ButtonsPage() {
<Section
id="sizes"
title="Размеры"
subtitle="Три размера для разных уровней иерархии интерфейса."
subtitle="Три размера для разных контекстов. lg соответствует форм-кнопке на реальном сайте (18px, bold)."
>
<div
className="rounded-xl border overflow-hidden"
@ -219,20 +332,20 @@ export default function ButtonsPage() {
{
size: "sm" as const,
label: "Small",
hint: "padding: 5px 12px · font: 13px · radius: 6px",
use: "Компактные интерфейсы, действия в таблицах",
hint: "4px 11px · 13px",
use: "Компактные контексты, таблицы",
},
{
size: "md" as const,
label: "Medium",
hint: "padding: 8px 18px · font: 14px · radius: 8px",
use: "Стандартный размер (по умолчанию)",
hint: "8px 16px · 14px",
use: "Appointment, Teal, Pill (соответствует сайту)",
},
{
size: "lg" as const,
label: "Large",
hint: "padding: 12px 26px · font: 16px · radius: 10px",
use: "Главные CTA на Hero-блоках",
hint: "10px 24px · 18px bold",
use: "Primary форм-кнопка (соответствует сайту)",
},
] as const
).map(({ size, label, hint, use }, i) => (
@ -241,7 +354,7 @@ export default function ButtonsPage() {
className="flex items-center gap-6 px-5 py-4"
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>
@ -251,12 +364,12 @@ export default function ButtonsPage() {
{label}
</p>
<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>
</div>
<p
className="text-xs hidden lg:block"
style={{ color: "var(--bb-text-muted)", maxWidth: 200 }}
style={{ color: "var(--bb-text-muted)", maxWidth: 220 }}
>
{use}
</p>
@ -269,49 +382,53 @@ export default function ButtonsPage() {
<Section
id="states"
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",
node: <Button variant="primary">Записаться</Button>,
node: <Button variant="primary" size="lg">Записаться</Button>,
hint: "Стандартное состояние",
},
{
label: "Hover",
node: (
<Button variant="primary" style={{ filter: "brightness(0.9)" }}>
<Button
variant="primary"
size="lg"
style={{ filter: "brightness(0.93)" }}
>
Записаться
</Button>
),
hint: "filter: brightness(0.9) при наведении курсора",
hint: "filter: brightness(0.93)",
},
{
label: "Loading",
node: <Button variant="primary" loading>Загрузка...</Button>,
hint: "Спиннер + opacity: 0.5 + кнопка заблокирована",
node: <Button variant="primary" size="lg" loading>Отправка...</Button>,
hint: "Спиннер + blocked",
},
{
label: "Disabled",
node: <Button variant="primary" disabled>Недоступно</Button>,
hint: "opacity: 0.5, cursor: not-allowed",
node: <Button variant="primary" size="lg" disabled>Записаться</Button>,
hint: "opacity: 0.5",
},
] as const
).map(({ label, node, hint }) => (
<div
key={label}
className="rounded-xl border p-5"
className="rounded-xl border p-4"
style={{ borderColor: "var(--bb-border)" }}
>
<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)" }}
>
{node}
</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}
</p>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
@ -322,51 +439,81 @@ export default function ButtonsPage() {
</div>
</Section>
{/* 4. Все варианты вместе */}
{/* 4. Контекст применения */}
<Section
id="all"
title="Все варианты и размеры"
subtitle="Сводная таблица — визуальное сравнение."
id="context"
title="Где применяется"
subtitle="Таблица: тип кнопки → реальное использование на сайте."
>
<div
className="rounded-xl border overflow-hidden"
style={{ borderColor: "var(--bb-border)" }}
>
<div
className="px-5 py-3 text-xs font-medium border-b"
style={{
color: "var(--bb-text-muted)",
background: "var(--bb-sidebar-bg)",
borderColor: "var(--bb-border)",
}}
<table className="w-full text-sm">
<thead>
<tr style={{ background: "var(--bb-sidebar-bg)" }}>
{["Вариант", "Цвет фона", "Реальный класс/контекст", "Текст кнопки на сайте"].map((h) => (
<th
key={h}
className="text-left px-4 py-3 font-medium text-xs"
style={{ color: "var(--bb-text-muted)" }}
>
Вариант / Размер
</div>
{VARIANT_INFO.map(({ variant, label }, vi) => (
<div
key={variant}
className="flex items-center gap-5 px-5 py-3"
style={{ borderTop: vi > 0 ? "1px solid var(--bb-border)" : undefined }}
{h}
</th>
))}
</tr>
</thead>
<tbody>
{[
{
v: <Button variant="primary" size="sm">Primary</Button>,
bg: "#FFA39C",
ctx: "button в entityform-блоках форм записи",
text: "«Запишите меня!»",
},
{
v: <Button variant="outline" size="sm">Outline</Button>,
bg: "#fff / рамка #BF9975",
ctx: ".appointment в хедере (block-block-15, 30, 32, 24)",
text: "«Записаться на прием»",
},
{
v: <Button variant="teal" size="sm">Teal</Button>,
bg: "#60959c",
ctx: ".show-phone (block-block-4, 15)",
text: "«Позвонить»",
},
{
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)" }}
>
<span
className="w-20 text-xs font-mono shrink-0"
<td className="px-4 py-3">{v}</td>
<td
className="px-4 py-3 font-mono text-xs"
style={{ color: "var(--bb-text-muted)" }}
>
{label}
</span>
<div className="flex items-center gap-3 flex-wrap">
<Button variant={variant} size="sm">
Маленькая
</Button>
<Button variant={variant} size="md">
Средняя
</Button>
<Button variant={variant} size="lg">
Большая
</Button>
</div>
</div>
{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>
</Section>
@ -374,58 +521,50 @@ export default function ButtonsPage() {
<Section
id="code"
title="Примеры кода"
subtitle="Скопируйте HTML или JSX для использования в проекте."
subtitle="HTML-классы из globals.css, JSX-компонент, и точный CSS с сайта."
>
<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="CSS — точно с сайта oclinica.ru" code={codeSiteExact} />
</div>
</Section>
{/* LLM-блок */}
<LlmBlock path="/components/buttons" version="v1.0" specText={LLM_BUTTONS_TEXT}>
<LlmSection title="Варианты" />
<LlmBlock path="/components/buttons" version="v2.0" specText={LLM_BUTTONS_TEXT}>
<LlmSection title="Варианты (реальный сайт oclinica.ru)" />
<LlmTable
headers={["Вариант", "CSS класс", "Фон", "Текст", "Применение"]}
rows={VARIANT_INFO.map((v) => [
headers={["Вариант", "CSS класс", "Фон", "Текст", "Border", "Radius", "Применение"]}
rows={VARIANTS.map((v) => [
v.variant,
v.cssClass,
v.bg,
v.text,
v.useCase,
v.textColor,
v.border,
v.radius,
v.where,
])}
/>
<LlmSection title="Размеры" />
<LlmTable
headers={["Размер", "CSS класс", "padding", "font-size", "radius", "Применение"]}
rows={[
["sm", ".bb-btn-sm", "5px 12px", "13px", "6px", "Компактные интерфейсы, таблицы"],
["md", ".bb-btn-md", "8px 18px", "14px", "8px", "Стандарт (по умолчанию)"],
["lg", ".bb-btn-lg", "12px 26px", "16px", "10px", "Hero CTA"],
]}
/>
<LlmSection title="Состояния" />
<LlmSection title="Размеры (брендбук-компонент)" />
<LlmTable
headers={["Состояние", "CSS / Поведение"]}
headers={["Размер", "padding", "font-size", "Применение"]}
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"],
["sm", "4px 11px", "13px", "Компактные контексты"],
["md", "8px 16px", "14px", "Стандарт (outline, teal, pill с сайта)"],
["lg", "10px 24px", "18px bold", "Primary форм-кнопка (соответствует сайту)"],
]}
/>
<LlmSection title="Правила применения" />
<LlmRules
rules={[
{ ok: true, text: "Не более одной primary-кнопки на один экран в контексте задачи" },
{ ok: true, text: "Текст — глагол или призыв: «Записаться», «Узнать цену»" },
{ ok: true, text: "Primary → главное действие формы или подтверждения" },
{ ok: true, text: "Ghost → отмена и навигационные ссылки без акцента" },
{ ok: true, text: "Danger → только деструктивные действия" },
{ ok: false, text: "Не менять цвета произвольно вне фирменной палитры" },
{ ok: false, text: "Не добавлять тени к кнопкам" },
{ ok: false, text: "Не использовать Danger для нейтральных действий" },
{ ok: true, text: "primary (коралловый) — только для submit в формах записи" },
{ ok: true, text: "outline (бежевый) — хедер, навигация, второстепенные ссылки" },
{ ok: true, text: "teal (бирюзовый) — телефонные и контактные действия" },
{ ok: true, text: "pill (кремовый) — открытие модальных окон / callback" },
{ ok: true, text: "Не более одного primary на форму" },
{ ok: false, text: "Не менять цвета вне указанной палитры сайта" },
{ ok: false, text: "Primary — не для навигационных ссылок" },
{ ok: false, text: "Не накладывать тень на outline, teal, pill" },
]}
/>
</LlmBlock>

48
apps/web/app/globals.css

@ -68,14 +68,46 @@ body {
.bb-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.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; }
.bb-btn-md { font-size: 14px; padding: 8px 18px; border-radius: 8px; border: 1.5px solid transparent; }
.bb-btn-lg { font-size: 16px; padding: 12px 26px; border-radius: 10px; border: 1.5px solid transparent; }
.bb-btn-primary { background: var(--brand-073m); color: #fff; border-color: var(--brand-073m); }
.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; }
.bb-btn-danger { background: #dc2626; color: #fff; border-color: #dc2626; }
/* Размеры — только padding и font-size, radius задаётся вариантом */
.bb-btn-sm { font-size: 13px; padding: 4px 11px; border: 1px 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; }
/* Варианты — цвета и радиус по реальному сайту oclinica.ru */
/* primary — коралловая форм-кнопка «Запишите меня!» */
.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) ───────────────────────────────── */
.bb-input,

2
apps/web/components/ui/Button.tsx

@ -2,7 +2,7 @@
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 interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {

Loading…
Cancel
Save