Files
mobile-app-prototip/src/screens/screens-dev.jsx
T
arazor72 61f7e5776d Sprint 4: developer design-system screens
Two service screens accessible via Tweaks screen selector:

- DEV · Палитра — every color role grouped (primary / accent / warm /
  status / text / surfaces) with swatch, role name, CSS var name,
  hex code, and usage note. Key-color banner at top. Dynamic values
  (primary/accent/warm/success-50/fg-4/p300) recompute from
  ctx.palette when the user flips the color in Tweaks.

- DEV · Примеры — rendered components (buttons, chips, surfaces,
  status blocks, text hierarchy, avatars, badges, inputs, segmented
  control, shadow/radius scale) each labeled with the CSS vars it
  uses, so devs can read the theme visually.

Plumbed the active palette object via ctx.palette from App.jsx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:02:00 +05:00

412 lines
22 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { I } from '../icons.jsx';
import { Avatar, ScreenHeader } from '../components.jsx';
// ─────────────────────────────────────────────────────────────
// DEV · Colors palette reference
// Shows every design-system color with role, CSS var, hex, and usage.
// Dynamic values (primary/accent/warm/etc.) come from ctx.palette and
// update automatically when user switches the accent in Tweaks.
// ─────────────────────────────────────────────────────────────
function ColorRow({ c }) {
return (
<div style={{
display: 'flex', gap: 12, alignItems: 'center',
padding: '10px 14px', borderBottom: '1px solid var(--c-divider)',
}}>
<div style={{
width: 54, height: 40, borderRadius: 8, flexShrink: 0,
background: c.hex, border: '1px solid rgba(0,0,0,0.08)',
}} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--c-fg-1)' }}>{c.name}</div>
<div style={{ fontSize: 10.5, color: 'var(--c-fg-3)', fontFamily: 'SF Mono, Menlo, Consolas, monospace', marginTop: 1 }}>{c.var}</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 2, lineHeight: 1.4 }}>{c.use}</div>
</div>
<div style={{
fontSize: 11, fontFamily: 'SF Mono, Menlo, Consolas, monospace',
color: 'var(--c-fg-2)', fontWeight: 700,
background: 'var(--c-bg)', padding: '4px 6px', borderRadius: 5,
flexShrink: 0,
}}>{c.hex.toUpperCase()}</div>
</div>
);
}
function ColorGroup({ title, note, colors }) {
return (
<div style={{ marginBottom: 16 }}>
<div style={{ padding: '0 4px 8px' }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: .6, color: 'var(--c-fg-3)' }}>{title} · {colors.length}</div>
{note && <div className="sub" style={{ fontSize: 11, marginTop: 3 }}>{note}</div>}
</div>
<div className="card" style={{ padding: 0 }}>
{colors.map((c, i) => (
<div key={c.name} style={{ borderBottom: i === colors.length - 1 ? 0 : undefined }}>
<ColorRow c={c} />
</div>
))}
</div>
</div>
);
}
export function DevColorsScreen({ nav, ctx }) {
const p = ctx.palette;
const groups = [
{
title: 'Primary · динамически',
note: 'Основная brand-гамма. Меняется при переключении палитры в Tweaks.',
colors: [
{ name: 'primary', var: '--c-primary', hex: p.primary, use: 'CTA, активные иконки, прогресс' },
{ name: 'primary-darker', var: '--c-primary-darker', hex: p.darker, use: 'Активное состояние, таббар, hover' },
{ name: 'primary-dark', var: '--c-primary-dark', hex: p.dark, use: 'Глубокие фоны: QR, градиент восстановления' },
{ name: 'primary-50', var: '--c-primary-50', hex: p.p50, use: 'Очень лёгкие подложки, фон AI-чата' },
{ name: 'primary-100', var: '--c-primary-100', hex: p.p100, use: 'Фоны иконок в плитках, чипы, hero статей' },
{ name: 'primary-200', var: '--c-primary-200', hex: p.p200, use: 'Бордеры, hover-стейты, рамки карточек' },
{ name: 'primary-300', var: '--c-primary-300', hex: p.p300, use: 'Промежуточный primary, декоративные бордеры' },
],
},
{
title: 'Accent · динамически',
colors: [
{ name: 'accent', var: '--c-accent', hex: p.accent, use: 'Красная CTA на таймлайне, бейджи непрочитанных, булавка' },
{ name: 'accent-dark', var: '--c-accent-dark', hex: p.accentDark, use: 'Hover accent-кнопок' },
{ name: 'accent-50', var: '--c-accent-50', hex: p.accent50, use: 'Фон hero «Беременность», плашки danger' },
],
},
{
title: 'Warm · динамически',
colors: [
{ name: 'warm-50', var: '--c-warm-50', hex: p.warm50, use: 'Плашки-предупреждения, subtle warm bg' },
{ name: 'warm-100', var: '--c-warm-100', hex: p.warm100, use: 'hero статей «Дети», чип «К.м.н.», профиль' },
{ name: 'warm-200', var: '--c-warm-200', hex: '#E8DCC5', use: 'Акценты beige, не меняется по палитре' },
{ name: 'warm-text', var: '--c-warm-text', hex: '#7A6A2E', use: 'Текст внутри warm-чипов, не меняется' },
],
},
{
title: 'Статус',
colors: [
{ name: 'success', var: '--c-success', hex: '#2E9B6B', use: 'Онлайн-индикаторы, success-иконки' },
{ name: 'success-50', var: '--c-success-50', hex: p.success50, use: 'Фон «Готово» чипов, финал теста слуха · динамически' },
{ name: 'warning', var: '--c-warning', hex: '#E8A13C', use: 'Иконки лекарств, «В работе» статус' },
{ name: 'warning-50', var: '--c-warning-50', hex: '#FBEFD8', use: 'Фон уведомлений-напоминаний' },
{ name: 'danger', var: '--c-danger', hex: '#D94141', use: 'Критические ошибки, аллергии (красные чипы)' },
],
},
{
title: 'Текст',
colors: [
{ name: 'fg-1', var: '--c-fg-1', hex: '#17242E', use: 'Основной текст, заголовки' },
{ name: 'fg-2', var: '--c-fg-2', hex: '#3E4C5D', use: 'Основной текст в body статей' },
{ name: 'fg-3', var: '--c-fg-3', hex: '#6B7A89', use: 'Приглушённый текст, sub-заголовки, подписи' },
{ name: 'fg-4', var: '--c-fg-4', hex: p.fg4, use: 'Плейсхолдеры, disabled, разделители · динамически' },
],
},
{
title: 'Поверхности / бордеры',
colors: [
{ name: 'bg', var: '--c-bg', hex: '#F7F9FB', use: 'Фон приложения (внутри рамки)' },
{ name: 'bg-card', var: '--c-bg-card', hex: '#FFFFFF', use: 'Фон карточек, модалок, инпутов' },
{ name: 'border', var: '--c-border', hex: '#EAF0F3', use: 'Бордер карточек, инпутов, кнопок-ghost' },
{ name: 'border-strong', var: '--c-border-strong', hex: '#D8E1E6', use: 'Усиленный бордер, disabled-точки плана' },
{ name: 'divider', var: '--c-divider', hex: '#F0F4F6', use: 'Разделители строк внутри карточек' },
],
},
];
return (
<div style={{ paddingBottom: 40 }}>
<ScreenHeader title="DEV · Палитра" subtitle={`Активна: ${p.lb}`} onBack={() => nav.pop()} />
{/* Header banner with current palette key colors */}
<div style={{ padding: '0 20px 16px' }}>
<div className="card" style={{ padding: 14, background: 'var(--c-primary-50)', border: '1px solid var(--c-primary-200)' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--c-primary-darker)', textTransform: 'uppercase', letterSpacing: .6, marginBottom: 8 }}>Ключевые цвета</div>
<div style={{ display: 'flex', gap: 6 }}>
{[p.primary, p.darker, p.dark, p.p100, p.warm100, p.accent, p.success50, p.p300].map((c, i) => (
<div key={i} style={{
flex: 1, height: 36, borderRadius: 6, background: c,
border: '1px solid rgba(0,0,0,0.08)',
}} />
))}
</div>
</div>
</div>
<div style={{ padding: '0 16px' }}>
{groups.map(g => <ColorGroup key={g.title} {...g} />)}
<div style={{
marginTop: 8, padding: 12, borderRadius: 10,
background: 'var(--c-warning-50)', display: 'flex', gap: 10, alignItems: 'flex-start',
}}>
<I.shield size={18} style={{ color: 'var(--c-warning)', flexShrink: 0, marginTop: 1 }} />
<div className="sub" style={{ fontSize: 12, lineHeight: 1.5 }}>
Экран служебный. Переключите палитру в Tweaks динамические значения (primary / accent / warm / success-50 / fg-4 / p300) пересчитываются автоматически.
</div>
</div>
<div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
<button onClick={() => nav.push('dev-examples')} className="btn-p" style={{ flex: 1 }}>
Примеры применения <I.arrow size={16} />
</button>
</div>
</div>
</div>
);
}
// ─────────────────────────────────────────────────────────────
// DEV · Usage examples
// Real rendered components with labels pointing to CSS vars used.
// ─────────────────────────────────────────────────────────────
function ExampleBlock({ title, hint, children }) {
return (
<div style={{ marginBottom: 16 }}>
<div style={{ padding: '0 4px 8px' }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: .6, color: 'var(--c-fg-3)' }}>{title}</div>
</div>
<div className="card" style={{ padding: 14 }}>
{children}
</div>
{hint && <div className="sub" style={{ fontSize: 11, padding: '6px 4px 0', lineHeight: 1.5 }}>{hint}</div>}
</div>
);
}
function VarTag({ children }) {
return (
<code style={{
display: 'inline-block', padding: '1px 6px', borderRadius: 4,
background: 'var(--c-bg)', border: '1px solid var(--c-border)',
fontFamily: 'SF Mono, Menlo, Consolas, monospace', fontSize: 10.5,
color: 'var(--c-fg-2)',
}}>{children}</code>
);
}
function ExampleRow({ children, label }) {
return (
<div style={{ padding: '10px 0', borderBottom: '1px solid var(--c-divider)' }}>
<div style={{ marginBottom: 8 }}>{children}</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
{label}
</div>
</div>
);
}
export function DevExamplesScreen({ nav }) {
return (
<div style={{ paddingBottom: 40 }}>
<ScreenHeader title="DEV · Примеры" subtitle="Где какой цвет" onBack={() => nav.pop()} />
<div style={{ padding: '0 16px' }}>
{/* BUTTONS */}
<ExampleBlock title="Кнопки">
<ExampleRow label={<><VarTag>--c-primary</VarTag> фон · <VarTag>#fff</VarTag> текст</>}>
<button className="btn-p">Записаться на приём</button>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-accent</VarTag> фон · для красной CTA</>}>
<button className="btn-p btn-accent">Записаться · срочно</button>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-primary-100</VarTag> фон · <VarTag>--c-primary-darker</VarTag> текст</>}>
<button className="btn-s"><I.chat size={14} /> Чат</button>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<button className="btn-g">Отмена</button>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
<VarTag>#fff</VarTag> фон · <VarTag>--c-border</VarTag> рамка · <VarTag>--c-fg-2</VarTag> текст
</div>
</div>
</ExampleBlock>
{/* CHIPS */}
<ExampleBlock title="Чипы">
<ExampleRow label={<><VarTag>--c-primary-100</VarTag> / <VarTag>--c-primary-darker</VarTag></>}>
<span className="chip">Обычный</span>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-warm-100</VarTag> / <VarTag>--c-warm-text</VarTag></>}>
<span className="chip chip-warm">К.м.н.</span>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-success-50</VarTag> / <VarTag>--c-success</VarTag></>}>
<span className="chip chip-success">Готово</span>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-accent-50</VarTag> / <VarTag>--c-accent</VarTag></>}>
<span className="chip chip-danger">Пенициллин</span>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<span className="chip chip-soft">+ добавить</span>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
<VarTag>--c-bg</VarTag> / <VarTag>--c-fg-3</VarTag> / <VarTag>--c-border</VarTag>
</div>
</div>
</ExampleBlock>
{/* CARDS / SURFACES */}
<ExampleBlock title="Поверхности">
<ExampleRow label={<><VarTag>#fff</VarTag> фон · <VarTag>--sh-sm</VarTag> тень</>}>
<div className="card" style={{ padding: 12 }}>
<div style={{ fontSize: 13, fontWeight: 700 }}>Карточка по умолчанию</div>
<div className="sub" style={{ fontSize: 11 }}>белый фон, лёгкая тень</div>
</div>
</ExampleRow>
<ExampleRow label={<>gradient (<VarTag>--c-primary-100</VarTag> <VarTag>--c-warm-100</VarTag>)</>}>
<div className="card" style={{
padding: 12,
background: 'linear-gradient(135deg, var(--c-primary-100), var(--c-warm-100))',
border: 0,
}}>
<div style={{ fontSize: 13, fontWeight: 700 }}>Карточка профиля / статистики</div>
</div>
</ExampleRow>
<ExampleRow label={<>solid <VarTag>--c-primary-darker</VarTag> · белый текст</>}>
<div style={{
padding: 14, borderRadius: 14,
background: 'var(--c-primary-darker)', color: '#fff',
}}>
<div style={{ fontSize: 13, fontWeight: 700 }}>CTA-карточка</div>
<div style={{ fontSize: 11, opacity: .85, marginTop: 2 }}>hero записи на главной</div>
</div>
</ExampleRow>
</ExampleBlock>
{/* STATUS */}
<ExampleBlock title="Статусы">
<ExampleRow label={<>success <VarTag>--c-success-50</VarTag> фон</>}>
<div style={{ background: 'var(--c-success-50)', padding: 10, borderRadius: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
<I.check size={16} style={{ color: 'var(--c-success)' }} sw={3} />
<span style={{ fontSize: 13, color: 'var(--c-success)', fontWeight: 700 }}>Успешно</span>
</div>
</ExampleRow>
<ExampleRow label={<>warning <VarTag>--c-warning-50</VarTag> фон</>}>
<div style={{ background: 'var(--c-warning-50)', padding: 10, borderRadius: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
<I.bell size={16} style={{ color: 'var(--c-warning)' }} />
<span style={{ fontSize: 13, color: 'var(--c-warning)', fontWeight: 700 }}>Напоминание</span>
</div>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<div style={{ background: 'var(--c-accent-50)', padding: 10, borderRadius: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
<I.shield size={16} style={{ color: 'var(--c-danger)' }} />
<span style={{ fontSize: 13, color: 'var(--c-danger)', fontWeight: 700 }}>Критично</span>
</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
danger <VarTag>--c-accent-50</VarTag> фон · <VarTag>--c-danger</VarTag> текст
</div>
</div>
</ExampleBlock>
{/* TYPOGRAPHY */}
<ExampleBlock title="Иерархия текста" hint="fg-1 → fg-2 → fg-3 → fg-4 — по убыванию важности. В Лагуне fg-4 переопределён на серый из скрина.">
<ExampleRow label={<><VarTag>--c-fg-1</VarTag> · основной заголовок</>}>
<div className="h-screen" style={{ fontSize: 22 }}>Заголовок экрана</div>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-fg-2</VarTag> · body-текст</>}>
<div style={{ fontSize: 14, color: 'var(--c-fg-2)', lineHeight: 1.5 }}>Основной параграф, тело статей и объяснений в карточках.</div>
</ExampleRow>
<ExampleRow label={<><VarTag>--c-fg-3</VarTag> · muted / подписи</>}>
<div className="sub" style={{ fontSize: 13 }}>Подпись, время, sub-текст</div>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<div style={{ fontSize: 13, color: 'var(--c-fg-4)' }}>Плейсхолдер, disabled-состояние</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
<VarTag>--c-fg-4</VarTag> · disabled, placeholder
</div>
</div>
</ExampleBlock>
{/* AVATARS & BADGES */}
<ExampleBlock title="Аватары и бейджи">
<ExampleRow label={<><VarTag>--c-primary-100</VarTag> / <VarTag>--c-primary-darker</VarTag> градиент</>}>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<Avatar init="АС" size={48} />
<Avatar init="СА" size={40} />
<Avatar init="МЛ" size={32} />
</div>
</ExampleRow>
<ExampleRow label={<>online · <VarTag>--c-success</VarTag> · белая рамка</>}>
<div style={{ position: 'relative', width: 48, height: 48 }}>
<Avatar init="СА" size={48} />
<span style={{
position: 'absolute', bottom: 1, right: 1,
width: 12, height: 12, borderRadius: 999,
background: 'var(--c-success)', border: '2px solid #fff',
}} />
</div>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<span style={{
background: 'var(--c-accent)', color: '#fff',
fontSize: 11, fontWeight: 700, padding: '3px 8px', borderRadius: 999,
}}>2</span>
<span style={{
background: 'var(--c-primary-100)', color: 'var(--c-primary-darker)',
fontSize: 10, fontWeight: 700, padding: '2px 6px', borderRadius: 5, letterSpacing: .5,
}}>AI</span>
</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
unread <VarTag>--c-accent</VarTag> · AI-бейдж <VarTag>--c-primary-100</VarTag> / <VarTag>--c-primary-darker</VarTag>
</div>
</div>
</ExampleBlock>
{/* INPUT */}
<ExampleBlock title="Формы">
<ExampleRow label={<><VarTag>#fff</VarTag> фон · <VarTag>--c-border</VarTag> рамка · placeholder <VarTag>--c-fg-4</VarTag></>}>
<div style={{
background: '#fff', borderRadius: 12, padding: '10px 14px',
display: 'flex', alignItems: 'center', gap: 10,
border: '1px solid var(--c-border)',
}}>
<I.search size={16} style={{ color: 'var(--c-fg-4)' }} />
<span style={{ color: 'var(--c-fg-4)', fontSize: 14 }}>Найти врача...</span>
</div>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<div className="seg">
<button className="on">Активно</button>
<button>Неактивно</button>
</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
segmented · <VarTag>#EEF2F5</VarTag> фон · активный <VarTag>#fff</VarTag> / <VarTag>--c-primary-darker</VarTag>
</div>
</div>
</ExampleBlock>
{/* Shadow / radius quick reference */}
<ExampleBlock title="Тени и радиусы">
<ExampleRow label={<><VarTag>--sh-sm</VarTag> · <VarTag>--r-lg</VarTag> = 16px</>}>
<div style={{
padding: 14, borderRadius: 'var(--r-lg)',
background: '#fff', boxShadow: 'var(--sh-sm)',
fontSize: 13, fontWeight: 700,
}}>card (sh-sm)</div>
</ExampleRow>
<ExampleRow label={<><VarTag>--sh-md</VarTag> · <VarTag>--r-xl</VarTag> = 20px</>}>
<div style={{
padding: 14, borderRadius: 'var(--r-xl)',
background: '#fff', boxShadow: 'var(--sh-md)',
fontSize: 13, fontWeight: 700,
}}>modal (sh-md)</div>
</ExampleRow>
<div style={{ padding: '10px 0' }}>
<div style={{
padding: 14, borderRadius: 'var(--r-2xl)',
background: '#fff', boxShadow: 'var(--sh-lg)',
fontSize: 13, fontWeight: 700,
}}>sheet (sh-lg)</div>
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
<VarTag>--sh-lg</VarTag> · <VarTag>--r-2xl</VarTag> = 24px
</div>
</div>
</ExampleBlock>
</div>
</div>
);
}