Add Launcher application template and icon system
This commit is contained in:
+392
-203
@@ -3,6 +3,8 @@ import { applyNodedcTheme, type NodedcTheme, type RgbTuple } from "@nodedc/ui-co
|
||||
import {
|
||||
AdminNavigationPanel,
|
||||
AppHeader,
|
||||
ApplicationPanel,
|
||||
ApplicationShell,
|
||||
Button,
|
||||
Checker,
|
||||
ColorField,
|
||||
@@ -11,24 +13,24 @@ import {
|
||||
Dropdown,
|
||||
FieldFrame,
|
||||
GlassSurface,
|
||||
HeaderAvatar,
|
||||
HeaderProfile,
|
||||
HeaderProfileButton,
|
||||
HeaderAvatar,
|
||||
HeaderWorkspace,
|
||||
Icon,
|
||||
IconButton,
|
||||
Inspector,
|
||||
RangeControl,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
StatusBadge,
|
||||
TextAreaField,
|
||||
TextField,
|
||||
Window,
|
||||
WindowFooterActions,
|
||||
type IconName,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
type CatalogSection = "foundation" | "controls" | "windows" | "inspector";
|
||||
type HubMode = "showcase" | "admin" | "platform";
|
||||
type CatalogSection = "foundation" | "controls" | "windows" | "modals" | "inspector" | "icons";
|
||||
|
||||
const engineAccentStyle = {
|
||||
"--nodedc-accent-rgb": "148 123 219",
|
||||
@@ -48,6 +50,123 @@ const selectOptions = [
|
||||
{ value: "disabled", label: "Отключён", description: "Запуск запрещён" },
|
||||
] as const;
|
||||
|
||||
const sectionDefinitions: Record<CatalogSection, { eyebrow: string; title: string; description: string; icon: IconName }> = {
|
||||
foundation: {
|
||||
eyebrow: "01 / FOUNDATION",
|
||||
title: "Основа",
|
||||
description: "Тема, акцент, стекло и неизменяемая геометрия приложения.",
|
||||
icon: "panel",
|
||||
},
|
||||
controls: {
|
||||
eyebrow: "02 / CONTROLS",
|
||||
title: "Контролы",
|
||||
description: "Кнопки, поля, selection, Engine-настройки и понятные статусы операций.",
|
||||
icon: "sliders",
|
||||
},
|
||||
windows: {
|
||||
eyebrow: "03 / WINDOWS",
|
||||
title: "Окна",
|
||||
description: "Обычные окна и modeless-панели с единым portal- и focus-контрактом.",
|
||||
icon: "apps",
|
||||
},
|
||||
modals: {
|
||||
eyebrow: "04 / MODALS",
|
||||
title: "Модалки",
|
||||
description: "Создание, подтверждение и разрушительные действия в адаптивном формате.",
|
||||
icon: "clipboard",
|
||||
},
|
||||
inspector: {
|
||||
eyebrow: "05 / INSPECTOR",
|
||||
title: "Инспектор",
|
||||
description: "Точный новый Engine Environment Settings без legacy-инспекторов.",
|
||||
icon: "settings",
|
||||
},
|
||||
icons: {
|
||||
eyebrow: "06 / ICONS",
|
||||
title: "Иконки",
|
||||
description: "Канонический общий набор по Launcher, SEO, BIM Viewer и новым участкам Engine.",
|
||||
icon: "grid",
|
||||
},
|
||||
};
|
||||
|
||||
const iconGroups: Array<{ title: string; note: string; icons: IconName[] }> = [
|
||||
{
|
||||
title: "Окно и слой",
|
||||
note: "Launcher / Hub",
|
||||
icons: ["close", "plus", "expand", "minimize", "refresh", "panel", "apps"],
|
||||
},
|
||||
{
|
||||
title: "Навигация",
|
||||
note: "Launcher / Engine / BIM",
|
||||
icons: ["chevron-left", "chevron-right", "chevron-down", "grid", "list", "sliders", "search"],
|
||||
},
|
||||
{
|
||||
title: "Редактирование",
|
||||
note: "Launcher / SEO",
|
||||
icons: ["save", "edit", "trash", "copy", "upload", "download", "external"],
|
||||
},
|
||||
{
|
||||
title: "Состояние и доступ",
|
||||
note: "Вся платформа",
|
||||
icons: ["check", "alert", "activity", "lock", "key", "shield", "circle"],
|
||||
},
|
||||
{
|
||||
title: "Сущности",
|
||||
note: "Общий словарь",
|
||||
icons: ["profile", "users", "building", "globe", "database", "network", "inbox", "mail"],
|
||||
},
|
||||
{
|
||||
title: "Контент",
|
||||
note: "SEO / BIM / Engine",
|
||||
icons: ["image", "video", "file", "folder", "clipboard", "settings"],
|
||||
},
|
||||
];
|
||||
|
||||
const iconLabels: Record<IconName, string> = {
|
||||
activity: "Активность",
|
||||
alert: "Предупреждение",
|
||||
apps: "Приложения",
|
||||
building: "Компания",
|
||||
check: "Готово",
|
||||
"chevron-down": "Раскрыть",
|
||||
"chevron-left": "Назад",
|
||||
"chevron-right": "Вперёд",
|
||||
circle: "Маркер",
|
||||
clipboard: "Список",
|
||||
close: "Закрыть",
|
||||
copy: "Копировать",
|
||||
database: "База данных",
|
||||
download: "Скачать",
|
||||
edit: "Редактировать",
|
||||
expand: "Развернуть",
|
||||
external: "Открыть снаружи",
|
||||
file: "Файл",
|
||||
folder: "Папка",
|
||||
globe: "Публичный контур",
|
||||
grid: "Обзор",
|
||||
image: "Изображение",
|
||||
inbox: "Уведомления",
|
||||
key: "Ключ",
|
||||
list: "Список задач",
|
||||
lock: "Закрыто",
|
||||
mail: "Приглашение",
|
||||
minimize: "Свернуть",
|
||||
network: "Связи",
|
||||
panel: "Панель",
|
||||
plus: "Добавить",
|
||||
profile: "Профиль",
|
||||
refresh: "Обновить",
|
||||
save: "Сохранить",
|
||||
search: "Поиск",
|
||||
settings: "Настройки",
|
||||
shield: "Доступ",
|
||||
sliders: "Параметры",
|
||||
trash: "Удалить",
|
||||
upload: "Загрузить",
|
||||
users: "Участники",
|
||||
video: "Видео",
|
||||
};
|
||||
|
||||
function rgbFromHex(hex: string): RgbTuple | null {
|
||||
const normalized = hex.trim().replace(/^#/, "");
|
||||
if (!/^[0-9a-f]{6}$/i.test(normalized)) return null;
|
||||
@@ -58,25 +177,6 @@ function rgbFromHex(hex: string): RgbTuple | null {
|
||||
];
|
||||
}
|
||||
|
||||
function Section({ id, eyebrow, title, description, children }: {
|
||||
id: string;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section id={id} className="catalog-section">
|
||||
<header className="catalog-section__head">
|
||||
<span>{eyebrow}</span>
|
||||
<h2>{title}</h2>
|
||||
<p>{description}</p>
|
||||
</header>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Preview({ title, note, children, className }: {
|
||||
title: string;
|
||||
note?: string;
|
||||
@@ -97,9 +197,9 @@ function Preview({ title, note, children, className }: {
|
||||
export function CatalogApp() {
|
||||
const [theme, setTheme] = useState<NodedcTheme>("dark");
|
||||
const [accentHex, setAccentHex] = useState("#c3ff66");
|
||||
const [section, setSection] = useState<CatalogSection>("foundation");
|
||||
const [hubMode, setHubMode] = useState<HubMode>("showcase");
|
||||
const [adminSection, setAdminSection] = useState("invites");
|
||||
const [guidelineOpen, setGuidelineOpen] = useState(false);
|
||||
const [activeSection, setActiveSection] = useState<CatalogSection | null>(null);
|
||||
const [panelExpanded, setPanelExpanded] = useState(true);
|
||||
const [selectedStatus, setSelectedStatus] = useState<(typeof selectOptions)[number]["value"]>("active");
|
||||
const [checker, setChecker] = useState(true);
|
||||
const [brightness, setBrightness] = useState(49);
|
||||
@@ -110,8 +210,9 @@ export function CatalogApp() {
|
||||
const [connectionColor, setConnectionColor] = useState("#404040");
|
||||
const [fillColor, setFillColor] = useState("#8f83ff");
|
||||
const [strokeColor, setStrokeColor] = useState("#2b2b36");
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [windowOpen, setWindowOpen] = useState(false);
|
||||
const [inspectorOpen, setInspectorOpen] = useState(false);
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [projectName, setProjectName] = useState("NODE.DC Platform");
|
||||
const [notes, setNotes] = useState("Общий контракт компонентов без доменной логики приложения.");
|
||||
@@ -122,9 +223,16 @@ export function CatalogApp() {
|
||||
applyNodedcTheme(document.documentElement, { theme, accent });
|
||||
}, [accent, theme]);
|
||||
|
||||
const openSection = (next: CatalogSection) => {
|
||||
setSection(next);
|
||||
document.getElementById(next)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
const openSection = (next: string) => {
|
||||
const section = next as CatalogSection;
|
||||
setGuidelineOpen(true);
|
||||
setActiveSection(section);
|
||||
setPanelExpanded(true);
|
||||
};
|
||||
|
||||
const closeGuideline = () => {
|
||||
setGuidelineOpen(false);
|
||||
setActiveSection(null);
|
||||
};
|
||||
|
||||
const inspectorSections = [
|
||||
@@ -161,9 +269,7 @@ export function CatalogApp() {
|
||||
</ControlRow>
|
||||
<Checker checked={usePortColors} label="Использовать цвет портов" onChange={setUsePortColors} />
|
||||
<ControlRow label="Цвет связей по умолчанию"><ColorField value={connectionColor} onChange={setConnectionColor} /></ControlRow>
|
||||
<ControlRow label="Применить ко всем">
|
||||
<Button variant="accent" shape="pill" width="full">Применить</Button>
|
||||
</ControlRow>
|
||||
<ControlRow label="Применить ко всем"><Button variant="accent" shape="pill" width="full">Применить</Button></ControlRow>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -182,102 +288,15 @@ export function CatalogApp() {
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="catalog-app nodedc-ui-root" style={{ "--catalog-accent": accentHex } as CSSProperties}>
|
||||
<AppHeader
|
||||
brand={<img src="/nodedc-logo.svg" alt="NODE.DC" />}
|
||||
center={
|
||||
<>
|
||||
<HeaderWorkspace label="DC" />
|
||||
<SegmentedControl
|
||||
label="Навигация Hub"
|
||||
value={hubMode}
|
||||
items={[
|
||||
{ value: "showcase", label: "Витрина" },
|
||||
{ value: "admin", label: "Администрирование" },
|
||||
{ value: "platform", label: "Платформа" },
|
||||
]}
|
||||
onChange={setHubMode}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
right={
|
||||
<HeaderProfile>
|
||||
<IconButton label="Уведомления">▱</IconButton>
|
||||
<HeaderProfileButton>Профиль</HeaderProfileButton>
|
||||
<HeaderAvatar label="DC" />
|
||||
</HeaderProfile>
|
||||
}
|
||||
/>
|
||||
|
||||
{hubMode === "admin" ? (
|
||||
<div className="catalog-admin-layer">
|
||||
<AdminNavigationPanel
|
||||
title="Администрирование"
|
||||
onClose={() => setHubMode("showcase")}
|
||||
contexts={[
|
||||
{
|
||||
id: "public",
|
||||
label: "Открытый контур",
|
||||
description: "Public access pool",
|
||||
icon: "◎",
|
||||
},
|
||||
{
|
||||
id: "company",
|
||||
groupLabel: "Компании",
|
||||
label: "DCTOUCH",
|
||||
description: "ООО ДИСТАЧ",
|
||||
icon: "▥",
|
||||
active: true,
|
||||
onSelect: () => undefined,
|
||||
onToggle: () => undefined,
|
||||
},
|
||||
]}
|
||||
items={[
|
||||
{ id: "overview", label: "Обзор", icon: "▦" },
|
||||
{ id: "invites", label: "Инвайты", icon: "✉" },
|
||||
{ id: "members", label: "Участники", icon: "♙" },
|
||||
{ id: "access", label: "Доступы", icon: "⌁" },
|
||||
{ id: "groups", label: "Группы", icon: "☷" },
|
||||
]}
|
||||
activeId={adminSection}
|
||||
onItemChange={setAdminSection}
|
||||
footer={
|
||||
<>
|
||||
<span className="nodedc-admin-panel__nav-icon" aria-hidden="true">♢</span>
|
||||
<span>Root Admin</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<main className="catalog-main" data-admin-open={hubMode === "admin" ? "true" : undefined}>
|
||||
<div className="catalog-hero">
|
||||
<StatusBadge tone="accent">NODE.DC UI baseline</StatusBadge>
|
||||
<h1>Один UI-контракт.<br />Разные приложения.</h1>
|
||||
<p>Геометрия, окна и поведение остаются общими. Приложение выбирает тему и акцент, затем использует готовые компоненты.</p>
|
||||
<div className="catalog-section-nav">
|
||||
<SegmentedControl
|
||||
label="Разделы каталога"
|
||||
value={section}
|
||||
items={[
|
||||
{ value: "foundation", label: "Основа" },
|
||||
{ value: "controls", label: "Контролы" },
|
||||
{ value: "windows", label: "Окна" },
|
||||
{ value: "inspector", label: "Инспектор" },
|
||||
]}
|
||||
onChange={openSection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section id="foundation" eyebrow="01 / Foundation" title="Материал и тема" description="Одинаковые поверхности проверяются в тёмной и светлой схеме без fork компонентов.">
|
||||
const renderSectionContent = () => {
|
||||
switch (activeSection) {
|
||||
case "foundation":
|
||||
return (
|
||||
<div className="catalog-grid catalog-grid--foundation">
|
||||
<Preview title="Theme contract" note={theme === "dark" ? "dark glass" : "light glass"}>
|
||||
<Preview title="Тема приложения" note={theme === "dark" ? "dark glass" : "light glass"}>
|
||||
<div className="catalog-theme-row">
|
||||
<Button variant={theme === "dark" ? "accent" : "secondary"} onClick={() => setTheme("dark")}>Dark</Button>
|
||||
<Button variant={theme === "light" ? "accent" : "secondary"} onClick={() => setTheme("light")}>Light</Button>
|
||||
<Button variant={theme === "dark" ? "accent" : "secondary"} shape="pill" onClick={() => setTheme("dark")}>Dark</Button>
|
||||
<Button variant={theme === "light" ? "accent" : "secondary"} shape="pill" onClick={() => setTheme("light")}>Light</Button>
|
||||
</div>
|
||||
<div className="catalog-swatches">
|
||||
{accents.map((item) => (
|
||||
@@ -295,34 +314,42 @@ export function CatalogApp() {
|
||||
</div>
|
||||
<ColorField label="Свой accent" value={accentHex} onChange={setAccentHex} />
|
||||
</Preview>
|
||||
<Preview title="Glass hierarchy" note="default / strong / soft">
|
||||
<Preview title="Иерархия стекла" note="default / strong / soft">
|
||||
<div className="catalog-surface-stack">
|
||||
<GlassSurface padding="md">Default surface</GlassSurface>
|
||||
<GlassSurface tone="strong" padding="md">Strong floating surface</GlassSurface>
|
||||
<GlassSurface tone="soft" padding="md">Soft nested surface</GlassSurface>
|
||||
</div>
|
||||
</Preview>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section id="controls" eyebrow="02 / Primitives" title="Контролы" description="Общие actions, fields и selection primitives для React и DOM consumers.">
|
||||
<div className="catalog-grid">
|
||||
<Preview title="Actions" note="shared geometry">
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<Button variant="accent" shape="pill">Сохранить</Button>
|
||||
<Button>Отмена</Button>
|
||||
<Button variant="ghost">Подробнее</Button>
|
||||
<Button variant="danger">Удалить</Button>
|
||||
<IconButton label="Настройки">⚙</IconButton>
|
||||
<Preview title="Каркас Launcher" note="fixed header / stage / panels" className="catalog-preview--wide">
|
||||
<div className="catalog-shell-diagram" aria-label="Схема каркаса приложения">
|
||||
<span className="catalog-shell-diagram__header">Header</span>
|
||||
<span className="catalog-shell-diagram__nav">Navigation</span>
|
||||
<span className="catalog-shell-diagram__content">Content window</span>
|
||||
<span className="catalog-shell-diagram__stage">Stage</span>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Fields" note="label / hint / control">
|
||||
</div>
|
||||
);
|
||||
case "controls":
|
||||
return (
|
||||
<div className="catalog-grid">
|
||||
<Preview title="Действия" note="общая геометрия">
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<Button variant="accent" shape="pill" icon={<Icon name="save" />}>Сохранить</Button>
|
||||
<Button icon={<Icon name="refresh" />}>Обновить</Button>
|
||||
<Button variant="ghost" icon={<Icon name="external" />}>Открыть</Button>
|
||||
<Button variant="danger" icon={<Icon name="trash" />}>Удалить</Button>
|
||||
<IconButton label="Добавить"><Icon name="plus" /></IconButton>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Поля" note="label / hint / control">
|
||||
<div className="catalog-form">
|
||||
<TextField label="Название проекта" hint="обязательно" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
|
||||
<TextAreaField label="Описание" value={notes} onChange={(event) => setNotes(event.target.value)} />
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Selection" note="portal dropdown">
|
||||
<Preview title="Selection" note="integrated / split">
|
||||
<div className="catalog-form">
|
||||
<FieldFrame label="Hub / integrated select">
|
||||
<Select label="Статус" value={selectedStatus} options={[...selectOptions]} searchable onChange={setSelectedStatus} />
|
||||
@@ -344,20 +371,20 @@ export function CatalogApp() {
|
||||
</FieldFrame>
|
||||
<Dropdown
|
||||
trigger={({ open, toggle, setTriggerRef, surfaceId }) => (
|
||||
<Button ref={setTriggerRef} aria-expanded={open} aria-controls={surfaceId} onClick={toggle}>Действия</Button>
|
||||
<Button ref={setTriggerRef} aria-expanded={open} aria-controls={surfaceId} icon={<Icon name="chevron-down" />} onClick={toggle}>Действия</Button>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><span /><span className="nodedc-dropdown-option__label">Переименовать</span></button>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><span /><span className="nodedc-dropdown-option__label">Дублировать</span></button>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><span /><span className="nodedc-dropdown-option__label">Архивировать</span></button>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="edit" /><span className="nodedc-dropdown-option__label">Переименовать</span></button>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="copy" /><span className="nodedc-dropdown-option__label">Дублировать</span></button>
|
||||
<button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="folder" /><span className="nodedc-dropdown-option__label">Архивировать</span></button>
|
||||
</>
|
||||
)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Environment controls" note="exact Engine geometry">
|
||||
<Preview title="Environment controls" note="точная геометрия Engine">
|
||||
<div className="catalog-engine-control-stack" style={engineAccentStyle}>
|
||||
<ControlRow label="Цвет"><ColorField value={lightColor} onChange={setLightColor} /></ControlRow>
|
||||
<RangeControl label="Яркость" value={brightness} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setBrightness} />
|
||||
@@ -365,77 +392,239 @@ export function CatalogApp() {
|
||||
<ControlRow label="Применить ко всем"><Button variant="accent" shape="pill" width="full">Применить</Button></ControlRow>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Semantic status" note="not tied to accent" className="catalog-preview--wide">
|
||||
<Preview title="Статусы операций" note="не цвет приложения" className="catalog-preview--wide">
|
||||
<p className="catalog-preview__explanation">Статус сообщает результат операции: нейтрально, успешно, требуется внимание или ошибка. Акцент приложения для этого не переиспользуется.</p>
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<StatusBadge>Черновик</StatusBadge>
|
||||
<StatusBadge tone="success">Готово</StatusBadge>
|
||||
<StatusBadge tone="success">Синхронизировано</StatusBadge>
|
||||
<StatusBadge tone="warning">Нужна проверка</StatusBadge>
|
||||
<StatusBadge tone="danger">Ошибка</StatusBadge>
|
||||
<StatusBadge tone="accent">В работе</StatusBadge>
|
||||
</div>
|
||||
</Preview>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section id="windows" eyebrow="03 / Layers" title="Окна и подтверждения" description="Modal и modeless side panel используют общий portal, Escape и focus restore; trap и scroll lock остаются только у modal.">
|
||||
<Preview title="Controlled windows" note="center / end / confirm">
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<Button variant="accent" onClick={() => setModalOpen(true)}>Открыть окно</Button>
|
||||
<Button onClick={() => setInspectorOpen(true)}>Открыть инспектор</Button>
|
||||
<Button variant="danger" onClick={() => setConfirmOpen(true)}>Проверить удаление</Button>
|
||||
</div>
|
||||
</Preview>
|
||||
</Section>
|
||||
|
||||
<Section id="inspector" eyebrow="04 / Pattern" title="Inspector / Settings" description="Канон основан только на новом Environment Settings и NDC Agent Inspector, без legacy-инспекторов Engine.">
|
||||
);
|
||||
case "windows":
|
||||
return (
|
||||
<div className="catalog-grid catalog-grid--single">
|
||||
<Preview title="Управляемые окна" note="center / modeless end">
|
||||
<p className="catalog-preview__explanation">Обычное окно блокирует фон и удерживает фокус. Правый инспектор не затемняет, не блокирует stage и не превращается в modal.</p>
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<Button variant="accent" shape="pill" icon={<Icon name="apps" />} onClick={() => setWindowOpen(true)}>Открыть окно</Button>
|
||||
<Button shape="pill" icon={<Icon name="panel" />} onClick={() => setInspectorOpen(true)}>Открыть инспектор</Button>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Оконные действия" note="круг `46.72 px`">
|
||||
<div className="catalog-window-actions-demo">
|
||||
<Button shape="pill" icon={<Icon name="refresh" />}>Обновить источник</Button>
|
||||
<IconButton label="Добавить"><Icon name="plus" /></IconButton>
|
||||
<IconButton label="Развернуть"><Icon name="expand" /></IconButton>
|
||||
<IconButton label="Закрыть"><Icon name="close" /></IconButton>
|
||||
</div>
|
||||
</Preview>
|
||||
</div>
|
||||
);
|
||||
case "modals":
|
||||
return (
|
||||
<div className="catalog-grid">
|
||||
<Preview title="Создание" note="форма адаптируется по ширине">
|
||||
<p className="catalog-preview__explanation">Поля переходят в одну колонку на узком viewport; footer остаётся видимым, прокручивается только тело окна.</p>
|
||||
<Button variant="accent" shape="pill" icon={<Icon name="plus" />} onClick={() => setCreateModalOpen(true)}>Создать проект</Button>
|
||||
</Preview>
|
||||
<Preview title="Подтверждение удаления" note="async-safe">
|
||||
<p className="catalog-preview__explanation">Destructive-цвет появляется у подтверждения, а не окрашивает всё окно заранее.</p>
|
||||
<Button variant="danger" shape="pill" icon={<Icon name="trash" />} onClick={() => setConfirmOpen(true)}>Проверить удаление</Button>
|
||||
</Preview>
|
||||
</div>
|
||||
);
|
||||
case "inspector":
|
||||
return (
|
||||
<div className="catalog-inspector-stage">
|
||||
<GlassSurface tone="strong" radius="panel" className="catalog-inspector-card" style={engineAccentStyle}>
|
||||
<div className="catalog-inspector-title"><strong>Настройки окружения</strong><span>ENGINE / новый канон</span></div>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["illumination", "connections", "ports"]} />
|
||||
</GlassSurface>
|
||||
</div>
|
||||
</Section>
|
||||
</main>
|
||||
);
|
||||
case "icons":
|
||||
return (
|
||||
<div className="catalog-icon-catalog">
|
||||
<Preview title="Размеры и поверхности" note="16 / 18 / 20 px" className="catalog-preview--wide">
|
||||
<div className="catalog-window-actions-demo">
|
||||
<IconButton label="Добавить"><Icon name="plus" size={16} /></IconButton>
|
||||
<IconButton label="Обновить"><Icon name="refresh" size={18} /></IconButton>
|
||||
<IconButton label="Развернуть"><Icon name="expand" size={20} /></IconButton>
|
||||
<IconButton label="Закрыть"><Icon name="close" size={18} /></IconButton>
|
||||
</div>
|
||||
<p className="catalog-preview__explanation">Иконка наследует `currentColor`; фон, размер кнопки и active-state задаёт компонент поверхности, а не SVG.</p>
|
||||
</Preview>
|
||||
{iconGroups.map((group) => (
|
||||
<section className="catalog-icon-group" key={group.title}>
|
||||
<header><strong>{group.title}</strong><span>{group.note}</span></header>
|
||||
<div className="catalog-icon-grid">
|
||||
{group.icons.map((name) => (
|
||||
<div className="catalog-icon-tile" key={name}>
|
||||
<span className="catalog-icon-tile__surface"><Icon name={name} size={20} /></span>
|
||||
<span><strong>{iconLabels[name]}</strong><code>{name}</code></span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
<Window
|
||||
open={modalOpen}
|
||||
title="Настройки сервиса"
|
||||
subtitle="Единое окно Launcher / CMS / SEO"
|
||||
onClose={() => setModalOpen(false)}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => setModalOpen(false)}>Отмена</Button>
|
||||
<WindowFooterActions><Button variant="accent" onClick={() => setModalOpen(false)}>Сохранить</Button></WindowFooterActions>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="catalog-form">
|
||||
<TextField label="Название" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
|
||||
<FieldFrame label="Статус"><Select label="Статус" value={selectedStatus} options={[...selectOptions]} onChange={setSelectedStatus} /></FieldFrame>
|
||||
<Checker checked={checker} label="Сервис активен" onChange={setChecker} />
|
||||
</div>
|
||||
</Window>
|
||||
const activeDefinition = activeSection ? sectionDefinitions[activeSection] : null;
|
||||
|
||||
<Window
|
||||
open={inspectorOpen}
|
||||
title="Настройки окружения"
|
||||
subtitle="ENGINE / новый канон"
|
||||
placement="end"
|
||||
style={engineAccentStyle}
|
||||
onClose={() => setInspectorOpen(false)}
|
||||
>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["illumination", "connections", "ports"]} />
|
||||
</Window>
|
||||
return (
|
||||
<>
|
||||
<ApplicationShell
|
||||
className="catalog-app nodedc-ui-root"
|
||||
style={{ "--catalog-accent": accentHex } as CSSProperties}
|
||||
navigationOpen={guidelineOpen}
|
||||
contentOpen={Boolean(activeSection)}
|
||||
contentExpanded={panelExpanded}
|
||||
header={
|
||||
<AppHeader
|
||||
brand={<img src="/nodedc-logo.svg" alt="NODE.DC" />}
|
||||
brandHref="/"
|
||||
center={
|
||||
<>
|
||||
<HeaderWorkspace className="catalog-header-workspace" label="NODE.DC Design" imageUrl="/nodedc-mark.svg" />
|
||||
<nav className="catalog-header-nav" aria-label="Навигация приложения">
|
||||
<button
|
||||
className="nodedc-header__nav-item"
|
||||
type="button"
|
||||
data-active={guidelineOpen ? "true" : undefined}
|
||||
aria-expanded={guidelineOpen}
|
||||
onClick={() => guidelineOpen ? closeGuideline() : setGuidelineOpen(true)}
|
||||
>
|
||||
Guideline
|
||||
</button>
|
||||
</nav>
|
||||
</>
|
||||
}
|
||||
right={
|
||||
<HeaderProfile>
|
||||
<IconButton label="Уведомления"><Icon name="inbox" /></IconButton>
|
||||
<HeaderProfileButton><Icon name="profile" size={17} />Профиль</HeaderProfileButton>
|
||||
<HeaderAvatar label="DC" />
|
||||
</HeaderProfile>
|
||||
}
|
||||
/>
|
||||
}
|
||||
stage={
|
||||
<section className="catalog-launcher-stage">
|
||||
<video className="catalog-launcher-stage__media" autoPlay muted loop playsInline poster="/launcher-stage-poster.png" aria-hidden="true">
|
||||
<source src="/launcher-stage.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<div className="catalog-launcher-stage__shade" />
|
||||
<div className="catalog-launcher-stage__title">
|
||||
<span>NODE.DC</span>
|
||||
<strong>DESIGN<br />GUIDELINE</strong>
|
||||
<p>Общий форм-фактор приложений, компоненты и оконная механика платформы.</p>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
navigation={
|
||||
<AdminNavigationPanel
|
||||
eyebrow="NODE.DC"
|
||||
title="Visual Guideline"
|
||||
closeLabel="Закрыть Guideline"
|
||||
navigationLabel="Разделы Guideline"
|
||||
onClose={closeGuideline}
|
||||
items={(Object.entries(sectionDefinitions) as Array<[CatalogSection, (typeof sectionDefinitions)[CatalogSection]]>).map(([id, item]) => ({
|
||||
id,
|
||||
label: item.title,
|
||||
icon: <Icon name={item.icon} />,
|
||||
}))}
|
||||
activeId={activeSection ?? undefined}
|
||||
onItemChange={openSection}
|
||||
footer={
|
||||
<>
|
||||
<span className="nodedc-admin-panel__nav-icon" aria-hidden="true"><Icon name="shield" /></span>
|
||||
<span>Design System 0.3</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
content={activeDefinition ? (
|
||||
<ApplicationPanel
|
||||
key={activeSection}
|
||||
eyebrow={activeDefinition.eyebrow}
|
||||
title={activeDefinition.title}
|
||||
description={activeDefinition.description}
|
||||
expanded={panelExpanded}
|
||||
onExpandedChange={setPanelExpanded}
|
||||
onClose={() => setActiveSection(null)}
|
||||
>
|
||||
<div className="catalog-panel-content">{renderSectionContent()}</div>
|
||||
</ApplicationPanel>
|
||||
) : null}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
open={confirmOpen}
|
||||
title="Удалить конфигурацию?"
|
||||
description={<>Конфигурация будет удалена. Это действие нельзя отменить.</>}
|
||||
confirmLabel="Удалить"
|
||||
danger
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => setConfirmOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
<Window
|
||||
open={windowOpen}
|
||||
title="Настройки сервиса"
|
||||
subtitle="Launcher / CMS / SEO"
|
||||
onClose={() => setWindowOpen(false)}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => setWindowOpen(false)}>Отмена</Button>
|
||||
<WindowFooterActions><Button variant="accent" shape="pill" onClick={() => setWindowOpen(false)}>Сохранить</Button></WindowFooterActions>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="catalog-form">
|
||||
<TextField label="Название" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
|
||||
<FieldFrame label="Статус"><Select label="Статус" value={selectedStatus} options={[...selectOptions]} onChange={setSelectedStatus} /></FieldFrame>
|
||||
<Checker checked={checker} label="Сервис активен" onChange={setChecker} />
|
||||
</div>
|
||||
</Window>
|
||||
|
||||
<Window
|
||||
open={inspectorOpen}
|
||||
title="Настройки окружения"
|
||||
subtitle="ENGINE / новый канон"
|
||||
placement="end"
|
||||
style={engineAccentStyle}
|
||||
onClose={() => setInspectorOpen(false)}
|
||||
>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["illumination", "connections", "ports"]} />
|
||||
</Window>
|
||||
|
||||
<Window
|
||||
open={createModalOpen}
|
||||
title="Создать проект"
|
||||
subtitle="Адаптивная форма"
|
||||
size="sm"
|
||||
onClose={() => setCreateModalOpen(false)}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => setCreateModalOpen(false)}>Отмена</Button>
|
||||
<WindowFooterActions><Button variant="accent" shape="pill" icon={<Icon name="plus" />} onClick={() => setCreateModalOpen(false)}>Создать</Button></WindowFooterActions>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="catalog-form">
|
||||
<TextField label="Название проекта" hint="обязательно" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
|
||||
<TextAreaField label="Описание" value={notes} onChange={(event) => setNotes(event.target.value)} />
|
||||
<FieldFrame label="Статус"><Select label="Статус" value={selectedStatus} options={[...selectOptions]} onChange={setSelectedStatus} /></FieldFrame>
|
||||
</div>
|
||||
</Window>
|
||||
|
||||
<ConfirmationModal
|
||||
open={confirmOpen}
|
||||
title="Удалить конфигурацию?"
|
||||
description={<>Конфигурация будет удалена. Это действие нельзя отменить.</>}
|
||||
confirmLabel="Удалить"
|
||||
danger
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => setConfirmOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+320
-120
@@ -2,22 +2,20 @@
|
||||
background: var(--nodedc-canvas);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background:
|
||||
radial-gradient(circle at 72% 10%, rgba(var(--nodedc-accent-rgb), 0.09), transparent 28rem),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px),
|
||||
linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
|
||||
var(--nodedc-canvas);
|
||||
background-size: auto, 44px 44px, 44px 44px, auto;
|
||||
background: #050506;
|
||||
color: var(--nodedc-text-primary);
|
||||
transition: background-color 220ms ease, color 220ms ease;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -30,96 +28,100 @@ textarea {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.catalog-admin-layer {
|
||||
position: fixed;
|
||||
z-index: 390;
|
||||
top: 6.55rem;
|
||||
bottom: 1.25rem;
|
||||
left: 1.25rem;
|
||||
.catalog-header-nav {
|
||||
display: inline-flex;
|
||||
min-height: var(--nodedc-header-pill-height);
|
||||
align-items: center;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: var(--nodedc-glass-panel-bg-soft);
|
||||
padding: 0.32rem;
|
||||
box-shadow: var(--nodedc-glass-control-shadow);
|
||||
}
|
||||
|
||||
[data-nodedc-theme="light"] .catalog-app .nodedc-header__brand img {
|
||||
.catalog-header-nav .nodedc-header__nav-item {
|
||||
min-width: 8.5rem;
|
||||
}
|
||||
|
||||
.catalog-header-workspace img {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
[data-nodedc-theme="light"] .catalog-app .nodedc-header__brand img,
|
||||
[data-nodedc-theme="light"] .catalog-app .nodedc-header__workspace img {
|
||||
filter: brightness(0.12);
|
||||
}
|
||||
|
||||
.catalog-main {
|
||||
.catalog-launcher-stage {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
border-radius: var(--nodedc-radius-card);
|
||||
background: #0b0b0d url("/launcher-stage-poster.png") center / cover no-repeat;
|
||||
box-shadow: 0 44px 150px rgba(0, 0, 0, 0.62);
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__media,
|
||||
.catalog-launcher-stage__shade {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__media {
|
||||
object-fit: cover;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__shade {
|
||||
z-index: 1;
|
||||
background:
|
||||
radial-gradient(circle at 52% 48%, transparent 0 38%, rgba(0, 0, 0, 0.34) 100%),
|
||||
linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent 38% 72%, rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: clamp(2.8rem, 9vh, 5.75rem);
|
||||
left: clamp(2.8rem, 6vw, 5.75rem);
|
||||
display: grid;
|
||||
width: min(1180px, calc(100% - 2rem));
|
||||
margin: 0 auto;
|
||||
gap: 8rem;
|
||||
padding: 5rem 0 10rem;
|
||||
transition: width 500ms cubic-bezier(0.22, 1, 0.36, 1), margin-left 500ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
max-width: 28rem;
|
||||
gap: 0.24rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.catalog-main[data-admin-open="true"] {
|
||||
width: min(1180px, calc(100% - 25rem));
|
||||
margin-right: 1rem;
|
||||
margin-left: 24rem;
|
||||
.catalog-launcher-stage__title span,
|
||||
.catalog-launcher-stage__title strong {
|
||||
font-size: clamp(2rem, 3vw, 3.25rem);
|
||||
font-weight: 350;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 0.98;
|
||||
}
|
||||
|
||||
.catalog-hero {
|
||||
.catalog-launcher-stage__title span {
|
||||
color: rgba(38, 38, 42, 0.76);
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title strong {
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title p {
|
||||
max-width: 22rem;
|
||||
margin: 0.9rem 0 0;
|
||||
color: rgba(255, 255, 255, 0.68);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.catalog-panel-content {
|
||||
display: grid;
|
||||
min-height: 32rem;
|
||||
align-content: center;
|
||||
justify-items: start;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
|
||||
.catalog-hero h1 {
|
||||
max-width: 820px;
|
||||
margin: 0;
|
||||
color: var(--nodedc-text-primary);
|
||||
font-size: clamp(3.1rem, 7vw, 6.4rem);
|
||||
font-weight: 670;
|
||||
letter-spacing: -0.065em;
|
||||
line-height: 0.92;
|
||||
}
|
||||
|
||||
.catalog-hero p {
|
||||
max-width: 650px;
|
||||
margin: 0;
|
||||
color: var(--nodedc-text-secondary);
|
||||
font-size: clamp(1rem, 1.6vw, 1.3rem);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.catalog-section-nav {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.catalog-section {
|
||||
display: grid;
|
||||
scroll-margin-top: 2rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.catalog-section__head {
|
||||
display: grid;
|
||||
max-width: 760px;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-section__head span {
|
||||
color: rgb(var(--nodedc-accent-rgb));
|
||||
font-size: 0.72rem;
|
||||
font-weight: 780;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.catalog-section__head h2 {
|
||||
margin: 0;
|
||||
font-size: clamp(2rem, 4vw, 3.6rem);
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.catalog-section__head p {
|
||||
margin: 0;
|
||||
color: var(--nodedc-text-secondary);
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
align-content: start;
|
||||
gap: 1rem;
|
||||
padding: 0.15rem 0.1rem 1rem;
|
||||
}
|
||||
|
||||
.catalog-grid {
|
||||
@@ -132,8 +134,12 @@ textarea {
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
}
|
||||
|
||||
.catalog-grid--single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.catalog-preview {
|
||||
min-height: 18rem;
|
||||
min-height: 16rem;
|
||||
}
|
||||
|
||||
.catalog-preview--wide {
|
||||
@@ -145,7 +151,7 @@ textarea {
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.catalog-preview__head strong {
|
||||
@@ -155,24 +161,35 @@ textarea {
|
||||
.catalog-preview__head span {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.68rem;
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.catalog-preview__body {
|
||||
display: grid;
|
||||
min-height: 10rem;
|
||||
min-height: 8rem;
|
||||
align-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.catalog-preview__explanation {
|
||||
max-width: 42rem;
|
||||
margin: 0;
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.catalog-theme-row,
|
||||
.catalog-inline {
|
||||
.catalog-inline,
|
||||
.catalog-window-actions-demo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.catalog-inline--wrap {
|
||||
.catalog-inline--wrap,
|
||||
.catalog-window-actions-demo {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -212,6 +229,14 @@ textarea {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-surface-stack .nodedc-glass {
|
||||
display: flex;
|
||||
min-height: 4rem;
|
||||
align-items: center;
|
||||
color: var(--nodedc-text-secondary);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.catalog-engine-control-stack {
|
||||
display: grid;
|
||||
width: min(var(--nodedc-inspector-inner-width), 100%);
|
||||
@@ -219,24 +244,49 @@ textarea {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-surface-stack .nodedc-glass {
|
||||
min-height: 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--nodedc-text-secondary);
|
||||
font-size: 0.78rem;
|
||||
.catalog-shell-diagram {
|
||||
display: grid;
|
||||
min-height: 16rem;
|
||||
grid-template-columns: minmax(8rem, 0.28fr) minmax(15rem, 1fr) minmax(7rem, 0.22fr);
|
||||
grid-template-rows: 3.2rem 1fr;
|
||||
gap: 0.6rem;
|
||||
border-radius: 1.1rem;
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram span {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 0.9rem;
|
||||
background: var(--nodedc-glass-control-bg);
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram__header {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram__nav {
|
||||
background: color-mix(in srgb, var(--nodedc-glass-control-hover) 90%, transparent) !important;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram__content {
|
||||
background: color-mix(in srgb, var(--nodedc-glass-control-hover) 70%, transparent) !important;
|
||||
}
|
||||
|
||||
.catalog-inspector-stage {
|
||||
display: flex;
|
||||
min-height: 40rem;
|
||||
min-height: 43rem;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
padding: 4rem;
|
||||
border-radius: 2rem;
|
||||
border-radius: var(--nodedc-radius-card);
|
||||
background:
|
||||
radial-gradient(circle at 18% 24%, rgba(var(--nodedc-accent-rgb), 0.13), transparent 22rem),
|
||||
color-mix(in srgb, var(--nodedc-canvas-soft) 86%, transparent);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.catalog-inspector-card {
|
||||
@@ -255,17 +305,89 @@ textarea {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.catalog-main {
|
||||
gap: 6rem;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
.catalog-icon-catalog {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.catalog-main[data-admin-open="true"] {
|
||||
width: min(1180px, calc(100% - 2rem));
|
||||
margin-inline: auto;
|
||||
}
|
||||
.catalog-icon-group {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
border-radius: var(--nodedc-radius-card);
|
||||
background: var(--nodedc-glass-panel-surface);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.catalog-icon-group > header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding-inline: 0.2rem;
|
||||
}
|
||||
|
||||
.catalog-icon-group > header strong {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.catalog-icon-group > header span {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.catalog-icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.catalog-icon-tile {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
grid-template-columns: 2.92rem minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
border-radius: var(--nodedc-radius-control);
|
||||
background: var(--nodedc-glass-control-bg);
|
||||
padding: 0.35rem 0.55rem 0.35rem 0.35rem;
|
||||
}
|
||||
|
||||
.catalog-icon-tile__surface {
|
||||
display: grid;
|
||||
width: 2.92rem;
|
||||
height: 2.92rem;
|
||||
place-items: center;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.catalog-icon-tile > span:last-child {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.18rem;
|
||||
}
|
||||
|
||||
.catalog-icon-tile strong,
|
||||
.catalog-icon-tile code {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.catalog-icon-tile strong {
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.catalog-icon-tile code {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.62rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.catalog-grid,
|
||||
.catalog-grid--foundation {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -275,14 +397,92 @@ textarea {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.catalog-icon-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.catalog-icon-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.catalog-launcher-stage__media {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-header-nav {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.catalog-header-nav .nodedc-header__nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.catalog-app .nodedc-header__profile-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title {
|
||||
top: 2rem;
|
||||
left: 2rem;
|
||||
max-width: calc(100% - 4rem);
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title span,
|
||||
.catalog-launcher-stage__title strong {
|
||||
font-size: clamp(1.8rem, 9vw, 3rem);
|
||||
}
|
||||
|
||||
.catalog-launcher-stage__title p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-preview {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.catalog-preview__head,
|
||||
.catalog-icon-group > header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.catalog-swatches,
|
||||
.catalog-icon-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.catalog-shell-diagram {
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
grid-template-rows: 3rem 7rem 7rem;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram__header {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.catalog-shell-diagram__stage {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.catalog-inspector-stage {
|
||||
padding: 1rem;
|
||||
min-height: 40rem;
|
||||
justify-content: center;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-window-actions-demo .nodedc-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-admin-layer {
|
||||
top: 0.875rem;
|
||||
right: 0.875rem;
|
||||
bottom: 0.875rem;
|
||||
left: 0.875rem;
|
||||
@media (max-width: 430px) {
|
||||
.catalog-swatches,
|
||||
.catalog-icon-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user