Browse Source
- /components/cards — new page with 5 card types + badges/tags/alerts + LLM block v1.0 - DoctorCard: photo 110×160px placeholder, name, specialty, experience, outline button - NewsCard: hover bg #eef4d1 + box-shadow (matches real site CSS), CSS class .bb-news-card - ReviewCard: star rating 1-5 (SVG), text 4-line clamp, bg #eef4d1 - PriceCard: service + price + description + button, highlighted variant (blue border/bg) - ServiceCard: emoji icon 48×48px, title, description, link - Badges: 6 color variants (primary/success/warning/danger/neutral/outline) - Tags: default/active state filters - Alerts: 4 types (info/success/warning/error) with icons - globals.css: .bb-news-card:hover, .bb-service-card:hover CSS rules (Sprint 4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>sprint/4
2 changed files with 636 additions and 0 deletions
@ -0,0 +1,629 @@
|
||||
import type { Metadata } from "next"; |
||||
import { CodeCopy } from "@/components/ui/CodeCopy"; |
||||
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock"; |
||||
|
||||
export const metadata: Metadata = { |
||||
title: "Карточки. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой", |
||||
}; |
||||
|
||||
function Section({ |
||||
id, title, subtitle, children, |
||||
}: { |
||||
id?: string; title: string; subtitle?: string; children: React.ReactNode; |
||||
}) { |
||||
return ( |
||||
<section id={id} className="mb-14"> |
||||
<div className="mb-6"> |
||||
<h2 className="text-xl font-semibold" style={{ color: "var(--bb-text)" }}>{title}</h2> |
||||
{subtitle && <p className="mt-1 text-sm" style={{ color: "var(--bb-text-muted)" }}>{subtitle}</p>} |
||||
</div> |
||||
{children} |
||||
</section> |
||||
); |
||||
} |
||||
|
||||
/* ─── Карточка врача ────────────────────────────────────────────────── */ |
||||
function DoctorCard({ |
||||
name, specialty, experience, photo, |
||||
}: { |
||||
name: string; specialty: string; experience: string; photo?: string; |
||||
}) { |
||||
return ( |
||||
<div |
||||
className="flex gap-4 p-4 rounded-xl border bg-white transition-shadow" |
||||
style={{ borderColor: "var(--bb-border)" }} |
||||
> |
||||
{/* Фото */} |
||||
<div |
||||
className="shrink-0 rounded-lg overflow-hidden" |
||||
style={{ width: 110, height: 160, background: "#dff0fa" }} |
||||
> |
||||
{photo ? ( |
||||
<img src={photo} alt={name} style={{ width: "100%", height: "100%", objectFit: "cover" }} /> |
||||
) : ( |
||||
<div |
||||
className="w-full h-full flex flex-col items-center justify-center gap-1" |
||||
style={{ color: "var(--brand-053m)", opacity: 0.5 }} |
||||
> |
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> |
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> |
||||
<circle cx="12" cy="7" r="4" /> |
||||
</svg> |
||||
<span className="text-xs">фото</span> |
||||
</div> |
||||
)} |
||||
</div> |
||||
|
||||
{/* Информация */} |
||||
<div className="flex flex-col justify-between min-w-0"> |
||||
<div> |
||||
<h3 className="font-semibold text-sm leading-tight mb-1" style={{ color: "var(--bb-text)" }}> |
||||
{name} |
||||
</h3> |
||||
<p className="text-xs mb-2" style={{ color: "var(--bb-text-muted)" }}>{specialty}</p> |
||||
<p className="text-xs" style={{ color: "var(--brand-053m)" }}>{experience}</p> |
||||
</div> |
||||
<button className="bb-btn bb-btn-sm bb-btn-outline mt-3 self-start"> |
||||
Записаться |
||||
</button> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/* ─── Карточка новости ──────────────────────────────────────────────── */ |
||||
function NewsCard({ |
||||
date, title, snippet, category, |
||||
}: { |
||||
date: string; title: string; snippet: string; category?: string; |
||||
}) { |
||||
return ( |
||||
<div |
||||
className="bb-news-card rounded-xl border overflow-hidden cursor-pointer transition-all" |
||||
style={{ borderColor: "var(--bb-border)", background: "#fff" }} |
||||
> |
||||
{/* Превью */} |
||||
<div |
||||
className="h-36 flex items-center justify-center" |
||||
style={{ background: "#f0f9ff" }} |
||||
> |
||||
{category && ( |
||||
<span |
||||
className="px-3 py-1 rounded-full text-xs font-semibold" |
||||
style={{ background: "var(--brand-053m)", color: "#fff" }} |
||||
> |
||||
{category} |
||||
</span> |
||||
)} |
||||
</div> |
||||
|
||||
{/* Контент */} |
||||
<div className="p-4"> |
||||
<p className="text-xs mb-2" style={{ color: "var(--bb-text-muted)" }}>{date}</p> |
||||
<h3 className="font-semibold text-sm leading-tight mb-2" style={{ color: "var(--bb-text)" }}> |
||||
{title} |
||||
</h3> |
||||
<p className="text-xs leading-relaxed mb-3" style={{ color: "var(--bb-text-muted)" }}> |
||||
{snippet} |
||||
</p> |
||||
<span className="text-xs font-medium" style={{ color: "var(--brand-053m)" }}> |
||||
Читать далее → |
||||
</span> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/* ─── Карточка отзыва ───────────────────────────────────────────────── */ |
||||
function ReviewCard({ |
||||
author, date, text, rating, doctor, |
||||
}: { |
||||
author: string; date: string; text: string; rating: number; doctor?: string; |
||||
}) { |
||||
return ( |
||||
<div |
||||
className="rounded-xl border p-5" |
||||
style={{ borderColor: "var(--bb-border)", background: "#eef4d1" }} |
||||
> |
||||
{/* Звёзды */} |
||||
<div className="flex gap-0.5 mb-3"> |
||||
{Array.from({ length: 5 }, (_, i) => ( |
||||
<svg key={i} width="16" height="16" viewBox="0 0 24 24" fill={i < rating ? "#f59e0b" : "none"} stroke="#f59e0b" strokeWidth="1.5"> |
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /> |
||||
</svg> |
||||
))} |
||||
<span className="text-xs ml-1 font-medium" style={{ color: "#92400e" }}>{rating}/5</span> |
||||
</div> |
||||
|
||||
{/* Текст */} |
||||
<p |
||||
className="text-sm leading-relaxed mb-4" |
||||
style={{ |
||||
color: "var(--bb-text)", |
||||
display: "-webkit-box", |
||||
WebkitLineClamp: 4, |
||||
WebkitBoxOrient: "vertical", |
||||
overflow: "hidden", |
||||
}} |
||||
> |
||||
{text} |
||||
</p> |
||||
|
||||
{/* Автор */} |
||||
<div className="flex items-end justify-between"> |
||||
<div> |
||||
<p className="text-sm font-semibold" style={{ color: "var(--bb-text)" }}>{author}</p> |
||||
{doctor && <p className="text-xs mt-0.5" style={{ color: "var(--bb-text-muted)" }}>Врач: {doctor}</p>} |
||||
</div> |
||||
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>{date}</p> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/* ─── Карточка цены ─────────────────────────────────────────────────── */ |
||||
function PriceCard({ |
||||
service, price, description, highlighted, |
||||
}: { |
||||
service: string; price: string; description?: string; highlighted?: boolean; |
||||
}) { |
||||
return ( |
||||
<div |
||||
className="rounded-xl border p-5 flex flex-col gap-3" |
||||
style={{ |
||||
borderColor: highlighted ? "var(--brand-053m)" : "var(--bb-border)", |
||||
background: highlighted ? "#f0f9ff" : "#fff", |
||||
}} |
||||
> |
||||
<div className="flex items-start justify-between gap-3"> |
||||
<p className="text-sm font-medium flex-1" style={{ color: "var(--bb-text)" }}>{service}</p> |
||||
<p className="text-lg font-bold shrink-0" style={{ color: highlighted ? "var(--brand-053m)" : "var(--bb-text)" }}> |
||||
{price} |
||||
</p> |
||||
</div> |
||||
{description && ( |
||||
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>{description}</p> |
||||
)} |
||||
<button className="bb-btn bb-btn-sm bb-btn-outline self-start"> |
||||
Записаться |
||||
</button> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/* ─── Карточка услуги ───────────────────────────────────────────────── */ |
||||
function ServiceCard({ |
||||
title, description, icon, |
||||
}: { |
||||
title: string; description: string; icon: string; |
||||
}) { |
||||
return ( |
||||
<div |
||||
className="bb-service-card rounded-xl border p-5 flex flex-col gap-3 cursor-pointer transition-shadow" |
||||
style={{ borderColor: "var(--bb-border)", background: "#fff" }} |
||||
> |
||||
<div |
||||
className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" |
||||
style={{ background: "#dff0fa" }} |
||||
> |
||||
{icon} |
||||
</div> |
||||
<div> |
||||
<h3 className="font-semibold text-sm mb-1" style={{ color: "var(--bb-text)" }}>{title}</h3> |
||||
<p className="text-xs leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>{description}</p> |
||||
</div> |
||||
<span className="text-xs font-medium" style={{ color: "var(--brand-053m)" }}> |
||||
Подробнее → |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/* ─── LLM текст ─────────────────────────────────────────────────────── */ |
||||
const LLM_CARDS_TEXT = `КАРТОЧКИ — LLM-спецификация
|
||||
Версия: v1.0 · /components/cards |
||||
|
||||
КАРТОЧКА ВРАЧА (DoctorCard) |
||||
Структура: фото (110×160px) + имя + специализация + опыт + кнопка «Записаться» |
||||
Фото: 110px × 160px, object-fit: cover, border-radius: 8px, фон-placeholder: #dff0fa |
||||
Кнопка: .bb-btn-outline .bb-btn-sm |
||||
Источник: .doctor на perm.oclinica.ru |
||||
|
||||
КАРТОЧКА НОВОСТИ (NewsCard) |
||||
Структура: превью (h=144px) + дата + заголовок + анонс (4 строки) + «Читать далее →» |
||||
Hover: background #eef4d1 + box-shadow 0 0 16px #9e9e9a |
||||
Источник: #block-views-last-news-block-1 .views-column на сайте (200×200px) |
||||
Без hover: background #fff, border 1px |
||||
|
||||
КАРТОЧКА ОТЗЫВА (ReviewCard) |
||||
Структура: рейтинг (звёзды 1–5) + текст (4 строки, overflow hidden) + автор + дата + врач |
||||
Фон: #eef4d1 (светло-жёлтый) — из реального CSS сайта |
||||
Звёзды: SVG polygon, filled #f59e0b |
||||
|
||||
КАРТОЧКА ЦЕНЫ (PriceCard) |
||||
Структура: услуга + цена (bold) + описание + кнопка |
||||
highlighted-вариант: border var(--brand-053m), bg #f0f9ff, цена тоже --brand-053m |
||||
|
||||
КАРТОЧКА УСЛУГИ (ServiceCard) |
||||
Структура: иконка (emoji, 48×48px, bg #dff0fa) + заголовок + описание + «Подробнее →» |
||||
Hover: box-shadow (0.5rem smth) |
||||
|
||||
БЕЙДЖИ (Badge) |
||||
Варианты: primary (#0089c3), success (#059669), warning (#d97706), danger (#dc2626), neutral (#6b7280) |
||||
Размер: text-xs, px-2.5 py-0.5, border-radius: full |
||||
CSS: inline-flex, font-weight: 600 |
||||
|
||||
ТЕГИ (Tag) |
||||
Варианты: default (border, text), filled (bg), removable (с кнопкой ×) |
||||
Цвет: --brand-053m или нейтральный (#e5e7eb bg) |
||||
Размер: text-xs, px-2 py-1, border-radius: 4px |
||||
|
||||
АЛЕРТЫ (Alert) |
||||
Варианты: info (#dff0fa фон, #075985 текст), success (#d1fae5, #065f46), warning (#fef3c7, #92400e), error (#fee2e2, #991b1b) |
||||
Структура: иконка (16×16px) + заголовок + описание |
||||
Без кнопки закрыть в базовом варианте |
||||
|
||||
ПРАВИЛА |
||||
✓ DoctorCard: всегда показывать фото-placeholder если нет фото (не ломать layout) |
||||
✓ ReviewCard: обрезать текст после 4 строк (WebkitLineClamp) |
||||
✓ PriceCard: highlighted = акционная или рекомендуемая позиция |
||||
✓ NewsCard: hover-эффект обязателен (#eef4d1 + box-shadow) |
||||
✓ Badge: не более 2–3 бейджей рядом |
||||
✓ Alert: одновременно не более 1 alert одного типа на экране |
||||
✕ Не смешивать типы карточек в одной сетке без заголовка секции |
||||
✕ Не использовать ReviewCard без рейтинга`.trim();
|
||||
|
||||
/* ─── Данные примеров ───────────────────────────────────────────────── */ |
||||
const DOCTORS = [ |
||||
{ |
||||
name: "Иванова Анна Сергеевна", |
||||
specialty: "Оториноларинголог (ЛОР), высшая категория", |
||||
experience: "Стаж 18 лет", |
||||
}, |
||||
{ |
||||
name: "Петров Дмитрий Александрович", |
||||
specialty: "Сурдолог, аудиолог", |
||||
experience: "Стаж 12 лет", |
||||
}, |
||||
{ |
||||
name: "Соколова Мария Ивановна", |
||||
specialty: "Детский ЛОР, ринолог", |
||||
experience: "Стаж 9 лет", |
||||
}, |
||||
]; |
||||
|
||||
const REVIEWS = [ |
||||
{ |
||||
author: "Елена К.", |
||||
date: "15 марта 2026", |
||||
rating: 5, |
||||
doctor: "Иванова А.С.", |
||||
text: "Очень довольна визитом. Доктор внимательно выслушала все жалобы, провела осмотр и объяснила причину заболевания. Назначила лечение, которое помогло уже через 3 дня. Рекомендую!", |
||||
}, |
||||
{ |
||||
author: "Михаил Р.", |
||||
date: "10 марта 2026", |
||||
rating: 4, |
||||
doctor: "Петров Д.А.", |
||||
text: "Хороший специалист, всё объяснил понятно. Подождал немного дольше, чем ожидал, но качество приёма на высоте. Буду обращаться снова.", |
||||
}, |
||||
]; |
||||
|
||||
const PRICES = [ |
||||
{ service: "Первичный приём ЛОР-врача", price: "1 500 ₽", description: "Включает осмотр и консультацию" }, |
||||
{ service: "Повторный приём", price: "1 000 ₽", description: "До 14 дней после первичного" }, |
||||
{ service: "Промывание миндалин", price: "800 ₽", highlighted: true, description: "Аппаратное — аккустический вакуум" }, |
||||
{ service: "Аудиометрия", price: "1 200 ₽", description: "Исследование слуха" }, |
||||
]; |
||||
|
||||
const SERVICES = [ |
||||
{ title: "Лечение ангины и тонзиллита", description: "Консервативное и хирургическое лечение заболеваний миндалин", icon: "🦷" }, |
||||
{ title: "Аудиология и сурдология", description: "Диагностика нарушений слуха, подбор слуховых аппаратов", icon: "👂" }, |
||||
{ title: "Детский ЛОР", description: "Специализация на лечении ЛОР-заболеваний у детей от 0 лет", icon: "👶" }, |
||||
{ title: "Ринология", description: "Лечение заболеваний носа и придаточных пазух", icon: "👃" }, |
||||
]; |
||||
|
||||
/* ─── Коды примеров ─────────────────────────────────────────────────── */ |
||||
const codeDoctorCard = `<!-- Карточка врача -->
|
||||
<div class="doctor-card"> |
||||
<img src="doctor.jpg" width="110" height="160" alt="ФИО" /> |
||||
<div class="doctor-info"> |
||||
<h3>Иванова Анна Сергеевна</h3> |
||||
<p class="specialty">Оториноларинголог, высшая категория</p> |
||||
<p class="experience">Стаж 18 лет</p> |
||||
<button class="bb-btn bb-btn-sm bb-btn-outline">Записаться</button> |
||||
</div> |
||||
</div> |
||||
|
||||
/* CSS с сайта oclinica.ru */ |
||||
.doctor .image { float:left; margin-right:20px; width:110px; height:160px; } |
||||
.doctor .item { float:left; width:170px; } |
||||
.doctor h3 { margin-top:0; height:32px; }`;
|
||||
|
||||
const codeNewsCard = `<!-- Карточка новости (сайт: 200×200px) -->
|
||||
<div class="news-card"> |
||||
<div class="news-preview">...</div> |
||||
<div class="news-body"> |
||||
<time>15 марта 2026</time> |
||||
<h3>Заголовок новости</h3> |
||||
<p>Краткий анонс...</p> |
||||
<a href="#">Читать далее →</a> |
||||
</div> |
||||
</div> |
||||
|
||||
/* CSS с сайта */ |
||||
#block-views-last-news-block-1 .views-column { |
||||
background: #fff; width: 200px; height: 200px; |
||||
margin: 15px 8px; padding: 15px; |
||||
} |
||||
#block-views-last-news-block-1 .views-column:hover { |
||||
background: #eef4d1; |
||||
box-shadow: 0px 0px 16px 0px #9e9e9a; |
||||
}`;
|
||||
|
||||
const codeReviewCard = `<!-- Карточка отзыва -->
|
||||
<div class="review-card"> |
||||
<div class="stars">★★★★★</div> |
||||
<p class="text">Текст отзыва (4 строки, overflow: hidden)...</p> |
||||
<div class="author"> |
||||
<span>Елена К.</span> |
||||
<time>15 марта 2026</time> |
||||
</div> |
||||
</div> |
||||
|
||||
/* Стиль брендбука (фон из CSS сайта) */ |
||||
.review-card { background: #eef4d1; border-radius: 12px; padding: 20px; }`;
|
||||
|
||||
const codePriceCard = `<!-- Карточка цены -->
|
||||
<div class="price-card"> |
||||
<div class="price-row"> |
||||
<span class="service">Первичный приём ЛОР-врача</span> |
||||
<strong class="price">1 500 ₽</strong> |
||||
</div> |
||||
<p class="description">Включает осмотр и консультацию</p> |
||||
<button class="bb-btn bb-btn-sm bb-btn-outline">Записаться</button> |
||||
</div>`;
|
||||
|
||||
const codeBadges = `<!-- Бейджи -->
|
||||
<span class="bb-badge bb-badge-primary">ЛОР</span> |
||||
<span class="bb-badge bb-badge-success">Принимает</span> |
||||
<span class="bb-badge bb-badge-warning">Ожидает</span> |
||||
<span class="bb-badge bb-badge-danger">Не принимает</span> |
||||
<span class="bb-badge bb-badge-neutral">Высшая категория</span> |
||||
|
||||
<!-- Теги --> |
||||
<button class="bb-tag">Ухо</button> |
||||
<button class="bb-tag">Горло</button> |
||||
<button class="bb-tag bb-tag-active">Нос</button> |
||||
|
||||
<!-- Алерт --> |
||||
<div class="bb-alert bb-alert-info"> |
||||
<span class="bb-alert-icon">ℹ</span> |
||||
<div> |
||||
<strong>Запись открыта</strong> |
||||
<p>Вы можете записаться онлайн или по телефону.</p> |
||||
</div> |
||||
</div>`;
|
||||
|
||||
export default function CardsPage() { |
||||
return ( |
||||
<div className="max-w-4xl mx-auto px-8 py-10"> |
||||
|
||||
{/* Заголовок */} |
||||
<div className="mb-10 pb-6 border-b" style={{ borderColor: "var(--bb-border)" }}> |
||||
<p className="text-xs font-semibold uppercase tracking-widest mb-2" style={{ color: "var(--brand-053m)" }}> |
||||
Компоненты → 3.3 |
||||
</p> |
||||
<h1 className="text-3xl font-semibold mb-3" style={{ color: "var(--bb-text)" }}> |
||||
Карточки |
||||
</h1> |
||||
<p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}> |
||||
Карточки врача, новости, отзыва, цены и услуги — основные блоки контента сайта. |
||||
Бейджи, теги и алерты — вспомогательные элементы. |
||||
</p> |
||||
</div> |
||||
|
||||
{/* 1. Карточки врачей */} |
||||
<Section |
||||
id="doctor" |
||||
title="Карточка врача" |
||||
subtitle="Фото 110×160px, имя, специализация, стаж, кнопка записи. Соответствует .doctor на сайте." |
||||
> |
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> |
||||
{DOCTORS.map(d => <DoctorCard key={d.name} {...d} />)} |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* 2. Карточки новостей */} |
||||
<Section |
||||
id="news" |
||||
title="Карточка новости" |
||||
subtitle="Hover: bg #eef4d1 + box-shadow. Источник: #block-views-last-news-block-1 на сайте." |
||||
> |
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> |
||||
<NewsCard |
||||
date="15 марта 2026" |
||||
category="ЛОР" |
||||
title="Как правильно промывать нос при насморке" |
||||
snippet="Промывание носа — эффективный метод лечения и профилактики острого ринита. Рассказываем о правильной технике." |
||||
/> |
||||
<NewsCard |
||||
date="10 марта 2026" |
||||
category="Аудиология" |
||||
title="Новый слуховой аппарат в нашей клинике" |
||||
snippet="Мы начали подбор и настройку слуховых аппаратов нового поколения — незаметных и точных." |
||||
/> |
||||
<NewsCard |
||||
date="5 марта 2026" |
||||
title="Весенняя профилактика ЛОР-заболеваний" |
||||
snippet="Апрель — период обострений. Рекомендации нашего специалиста по укреплению иммунитета и защите." |
||||
/> |
||||
</div> |
||||
<p className="mt-3 text-xs" style={{ color: "var(--bb-text-muted)" }}> |
||||
* Наведите на карточку чтобы увидеть hover-эффект |
||||
</p> |
||||
</Section> |
||||
|
||||
{/* 3. Карточки отзывов */} |
||||
<Section |
||||
id="review" |
||||
title="Карточка отзыва" |
||||
subtitle="Рейтинг (1–5 звёзд), текст 4 строки, автор, дата, врач. Фон #eef4d1 — с реального сайта." |
||||
> |
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> |
||||
{REVIEWS.map(r => <ReviewCard key={r.author} {...r} />)} |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* 4. Карточки цен */} |
||||
<Section |
||||
id="price" |
||||
title="Карточка цены" |
||||
subtitle="Услуга + стоимость + описание + кнопка. Highlighted-вариант для акционных позиций." |
||||
> |
||||
<div className="flex flex-col gap-3"> |
||||
{PRICES.map(p => <PriceCard key={p.service} {...p} />)} |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* 5. Карточки услуг */} |
||||
<Section |
||||
id="service" |
||||
title="Карточка услуги" |
||||
subtitle="Иконка + заголовок + описание + ссылка. Применяется в блоке «Наши услуги»." |
||||
> |
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4"> |
||||
{SERVICES.map(s => <ServiceCard key={s.title} {...s} />)} |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* 6. Бейджи и теги */} |
||||
<Section |
||||
id="badges" |
||||
title="Бейджи и теги" |
||||
subtitle="Статусные бейджи, теги-категории, алерты." |
||||
> |
||||
{/* Бейджи */} |
||||
<div className="mb-8"> |
||||
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Бейджи (статус)</p> |
||||
<div className="flex flex-wrap gap-2"> |
||||
{[ |
||||
{ label: "Основной", bg: "var(--brand-053m)", color: "#fff" }, |
||||
{ label: "Принимает", bg: "#059669", color: "#fff" }, |
||||
{ label: "Высшая категория", bg: "#d97706", color: "#fff" }, |
||||
{ label: "Не принимает", bg: "#dc2626", color: "#fff" }, |
||||
{ label: "Нейтральный", bg: "#6b7280", color: "#fff" }, |
||||
{ label: "Новинка", bg: "#dff0fa", color: "var(--brand-053m)" }, |
||||
].map(b => ( |
||||
<span |
||||
key={b.label} |
||||
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold" |
||||
style={{ background: b.bg, color: b.color }} |
||||
> |
||||
{b.label} |
||||
</span> |
||||
))} |
||||
</div> |
||||
</div> |
||||
|
||||
{/* Теги */} |
||||
<div className="mb-8"> |
||||
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Теги (категории)</p> |
||||
<div className="flex flex-wrap gap-2"> |
||||
{["Ухо", "Горло", "Нос", "Аудиология", "Детский ЛОР", "Хирургия"].map((tag, i) => ( |
||||
<button |
||||
key={tag} |
||||
className="inline-flex items-center px-3 py-1 rounded text-xs font-medium border transition-colors" |
||||
style={ |
||||
i === 0 |
||||
? { background: "var(--brand-053m)", color: "#fff", borderColor: "var(--brand-053m)" } |
||||
: { background: "#fff", color: "var(--bb-text)", borderColor: "var(--bb-border)" } |
||||
} |
||||
> |
||||
{tag} |
||||
</button> |
||||
))} |
||||
</div> |
||||
</div> |
||||
|
||||
{/* Алерты */} |
||||
<div> |
||||
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Алерты</p> |
||||
<div className="flex flex-col gap-3"> |
||||
{[ |
||||
{ type: "info", bg: "#dff0fa", color: "#075985", icon: "ℹ", title: "Информация", text: "Запись открыта. Вы можете записаться онлайн или по телефону." }, |
||||
{ type: "success", bg: "#d1fae5", color: "#065f46", icon: "✓", title: "Успешно", text: "Ваша запись подтверждена. Ждём вас 20 марта в 10:00." }, |
||||
{ type: "warning", bg: "#fef3c7", color: "#92400e", icon: "⚠", title: "Внимание", text: "Не забудьте взять паспорт и полис ОМС на приём." }, |
||||
{ type: "error", bg: "#fee2e2", color: "#991b1b", icon: "✕", title: "Ошибка", text: "К сожалению, это время уже занято. Выберите другое." }, |
||||
].map(a => ( |
||||
<div |
||||
key={a.type} |
||||
className="flex gap-3 p-4 rounded-xl border" |
||||
style={{ background: a.bg, borderColor: a.bg, color: a.color }} |
||||
> |
||||
<span className="text-base font-bold shrink-0 mt-0.5">{a.icon}</span> |
||||
<div> |
||||
<p className="text-sm font-semibold mb-0.5">{a.title}</p> |
||||
<p className="text-sm opacity-90">{a.text}</p> |
||||
</div> |
||||
</div> |
||||
))} |
||||
</div> |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* 7. Код */} |
||||
<Section |
||||
id="code" |
||||
title="Примеры кода" |
||||
subtitle="HTML-структура и CSS-справка." |
||||
> |
||||
<div className="space-y-4"> |
||||
<CodeCopy lang="HTML — DoctorCard + CSS с сайта" code={codeDoctorCard} /> |
||||
<CodeCopy lang="HTML — NewsCard + CSS с сайта" code={codeNewsCard} /> |
||||
<CodeCopy lang="HTML — ReviewCard" code={codeReviewCard} /> |
||||
<CodeCopy lang="HTML — PriceCard" code={codePriceCard} /> |
||||
<CodeCopy lang="HTML — Badges, Tags, Alerts" code={codeBadges} /> |
||||
</div> |
||||
</Section> |
||||
|
||||
{/* LLM-блок */} |
||||
<LlmBlock path="/components/cards" version="v1.0" specText={LLM_CARDS_TEXT}> |
||||
<LlmSection title="Типы карточек" /> |
||||
<LlmTable |
||||
headers={["Карточка", "Ключевые размеры", "Источник на сайте", "Фон / hover"]} |
||||
rows={[ |
||||
["DoctorCard", "фото 110×160px, layout: flex", ".doctor .image + .doctor .item", "#fff / —"], |
||||
["NewsCard", "preview h=144px, grid 3 col", "#block-views-last-news-block-1 .views-column", "#fff / #eef4d1 + shadow"], |
||||
["ReviewCard", "4 строки текста, рейтинг", ".node-reviews", "#eef4d1 / —"], |
||||
["PriceCard", "flex row: name + price", ".field-name-field-price-priem", "#fff / highlighted: #f0f9ff"], |
||||
["ServiceCard", "иконка 48×48, grid 4 col", "—", "#fff / shadow"], |
||||
]} |
||||
/> |
||||
<LlmSection title="Бейджи, теги, алерты" /> |
||||
<LlmTable |
||||
headers={["Элемент", "Варианты", "Размер", "Применение"]} |
||||
rows={[ |
||||
["Badge", "primary / success / warning / danger / neutral", "text-xs, px-2.5, rounded-full", "Статус врача, категория, акция"], |
||||
["Tag", "default / active", "text-xs, px-3, rounded-4px", "Фильтры, категории услуг"], |
||||
["Alert", "info / success / warning / error", "p-4, border-radius 12px", "Системные сообщения пользователю"], |
||||
]} |
||||
/> |
||||
<LlmSection title="Правила применения" /> |
||||
<LlmRules |
||||
rules={[ |
||||
{ ok: true, text: "DoctorCard: всегда фото-placeholder если нет фото" }, |
||||
{ ok: true, text: "NewsCard: hover #eef4d1 + box-shadow (из реального CSS сайта)" }, |
||||
{ ok: true, text: "ReviewCard: обрезать текст после 4 строк (WebkitLineClamp: 4)" }, |
||||
{ ok: true, text: "PriceCard highlighted = акционная / рекомендуемая позиция" }, |
||||
{ ok: true, text: "Alert: один тип одновременно на экране" }, |
||||
{ ok: false, text: "Не смешивать типы карточек в одной сетке без заголовка" }, |
||||
{ ok: false, text: "Не использовать ReviewCard без рейтинга" }, |
||||
{ ok: false, text: "Не ставить более 3 бейджей рядом" }, |
||||
]} |
||||
/> |
||||
</LlmBlock> |
||||
|
||||
</div> |
||||
); |
||||
} |
||||
Loading…
Reference in new issue