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:
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
Reference in New Issue
Block a user