Correct Hub and Engine component geometry
This commit is contained in:
+166
-56
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import { applyNodedcTheme, type NodedcTheme, type RgbTuple } from "@nodedc/ui-core";
|
||||
import {
|
||||
AdminNavigationPanel,
|
||||
AppHeader,
|
||||
Button,
|
||||
Checker,
|
||||
@@ -11,6 +12,8 @@ import {
|
||||
FieldFrame,
|
||||
GlassSurface,
|
||||
HeaderProfile,
|
||||
HeaderProfileButton,
|
||||
HeaderAvatar,
|
||||
HeaderWorkspace,
|
||||
IconButton,
|
||||
Inspector,
|
||||
@@ -25,6 +28,12 @@ import {
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
type CatalogSection = "foundation" | "controls" | "windows" | "inspector";
|
||||
type HubMode = "showcase" | "admin" | "platform";
|
||||
|
||||
const engineAccentStyle = {
|
||||
"--nodedc-accent-rgb": "148 123 219",
|
||||
"--nodedc-on-accent-rgb": "247 248 244",
|
||||
} as CSSProperties;
|
||||
|
||||
const accents: Array<{ label: string; value: RgbTuple; hex: string }> = [
|
||||
{ label: "Hub", value: [195, 255, 102], hex: "#c3ff66" },
|
||||
@@ -89,9 +98,18 @@ 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 [selectedStatus, setSelectedStatus] = useState<(typeof selectOptions)[number]["value"]>("active");
|
||||
const [checker, setChecker] = useState(true);
|
||||
const [range, setRange] = useState(64);
|
||||
const [brightness, setBrightness] = useState(49);
|
||||
const [glowDistance, setGlowDistance] = useState(105);
|
||||
const [usePortColors, setUsePortColors] = useState(false);
|
||||
const [connectionType, setConnectionType] = useState("spline");
|
||||
const [lightColor, setLightColor] = useState("#7a3cff");
|
||||
const [connectionColor, setConnectionColor] = useState("#404040");
|
||||
const [fillColor, setFillColor] = useState("#8f83ff");
|
||||
const [strokeColor, setStrokeColor] = useState("#2b2b36");
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [inspectorOpen, setInspectorOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
@@ -111,40 +129,56 @@ export function CatalogApp() {
|
||||
|
||||
const inspectorSections = [
|
||||
{
|
||||
id: "definition",
|
||||
label: "Definition Control",
|
||||
description: "Параметры агентной ноды",
|
||||
id: "illumination",
|
||||
label: "Подсветка",
|
||||
tone: "accent" as const,
|
||||
content: (
|
||||
<>
|
||||
<ControlRow label="Активность"><Checker checked={checker} label="Агент активен" onChange={setChecker} /></ControlRow>
|
||||
<ControlRow label="Модель">
|
||||
<Select
|
||||
label="Модель"
|
||||
value="codex"
|
||||
options={[
|
||||
{ value: "codex", label: "Codex" },
|
||||
{ value: "local", label: "Local executor" },
|
||||
]}
|
||||
onChange={() => undefined}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl label="Температура" value={range} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setRange} />
|
||||
<ControlRow label="Цвет"><ColorField value={lightColor} onChange={setLightColor} /></ControlRow>
|
||||
<RangeControl label="Яркость" value={brightness} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setBrightness} />
|
||||
<RangeControl label="Дистанция свечения" value={glowDistance} min={0} max={200} formatValue={(value) => `${value}%`} onChange={setGlowDistance} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "appearance",
|
||||
label: "Внешний вид",
|
||||
description: "Тема и акцент",
|
||||
id: "connections",
|
||||
label: "Связи",
|
||||
tone: "accent" as const,
|
||||
content: (
|
||||
<ControlRow label="Акцент"><ColorField value={accentHex} onChange={setAccentHex} /></ControlRow>
|
||||
<>
|
||||
<ControlRow label="Связи по умолчанию" layout="stack">
|
||||
<Select
|
||||
variant="split"
|
||||
label="Тип связи"
|
||||
value={connectionType}
|
||||
options={[
|
||||
{ value: "spline", label: "Сплайн" },
|
||||
{ value: "straight", label: "Прямая" },
|
||||
{ value: "step", label: "Ступенчатая" },
|
||||
]}
|
||||
onChange={setConnectionType}
|
||||
/>
|
||||
</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>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "runtime",
|
||||
label: "Выполнение",
|
||||
description: "Параметры запуска",
|
||||
content: <Checker checked={false} label="Запускать автоматически" onChange={() => undefined} />,
|
||||
id: "ports",
|
||||
label: "Порты",
|
||||
tone: "accent" as const,
|
||||
content: (
|
||||
<>
|
||||
<ControlRow label="Заливка"><ColorField value={fillColor} onChange={setFillColor} /></ControlRow>
|
||||
<RangeControl label="Прозрачность" value={100} min={0} max={100} formatValue={(value) => `${value}%`} onChange={() => undefined} />
|
||||
<ControlRow label="Обводка"><ColorField value={strokeColor} onChange={setStrokeColor} /></ControlRow>
|
||||
<RangeControl label="Прозрачность" value={100} min={0} max={100} formatValue={(value) => `${value}%`} onChange={() => undefined} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -154,7 +188,76 @@ export function CatalogApp() {
|
||||
brand={<img src="/nodedc-logo.svg" alt="NODE.DC" />}
|
||||
center={
|
||||
<>
|
||||
<HeaderWorkspace label="UI" />
|
||||
<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}
|
||||
@@ -166,23 +269,7 @@ export function CatalogApp() {
|
||||
]}
|
||||
onChange={openSection}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
right={
|
||||
<HeaderProfile>
|
||||
<IconButton label="Сменить тему" onClick={() => setTheme((current) => current === "dark" ? "light" : "dark")}>
|
||||
{theme === "dark" ? "☼" : "◐"}
|
||||
</IconButton>
|
||||
<span className="catalog-avatar">DC</span>
|
||||
</HeaderProfile>
|
||||
}
|
||||
/>
|
||||
|
||||
<main className="catalog-main">
|
||||
<div className="catalog-hero">
|
||||
<StatusBadge tone="accent">baseline 0.1.0</StatusBadge>
|
||||
<h1>Один UI-контракт.<br />Разные приложения.</h1>
|
||||
<p>Геометрия, окна и поведение остаются общими. Приложение выбирает тему и акцент, затем использует готовые компоненты.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section id="foundation" eyebrow="01 / Foundation" title="Материал и тема" description="Одинаковые поверхности проверяются в тёмной и светлой схеме без fork компонентов.">
|
||||
@@ -222,7 +309,7 @@ export function CatalogApp() {
|
||||
<div className="catalog-grid">
|
||||
<Preview title="Actions" note="shared geometry">
|
||||
<div className="catalog-inline catalog-inline--wrap">
|
||||
<Button variant="accent">Сохранить</Button>
|
||||
<Button variant="accent" shape="pill">Сохранить</Button>
|
||||
<Button>Отмена</Button>
|
||||
<Button variant="ghost">Подробнее</Button>
|
||||
<Button variant="danger">Удалить</Button>
|
||||
@@ -237,9 +324,24 @@ export function CatalogApp() {
|
||||
</Preview>
|
||||
<Preview title="Selection" note="portal dropdown">
|
||||
<div className="catalog-form">
|
||||
<FieldFrame label="Статус">
|
||||
<FieldFrame label="Hub / integrated select">
|
||||
<Select label="Статус" value={selectedStatus} options={[...selectOptions]} searchable onChange={setSelectedStatus} />
|
||||
</FieldFrame>
|
||||
<FieldFrame label="Engine / split select">
|
||||
<div style={engineAccentStyle}>
|
||||
<Select
|
||||
variant="split"
|
||||
label="Тип связи"
|
||||
value={connectionType}
|
||||
options={[
|
||||
{ value: "spline", label: "Сплайн" },
|
||||
{ value: "straight", label: "Прямая" },
|
||||
{ value: "step", label: "Ступенчатая" },
|
||||
]}
|
||||
onChange={setConnectionType}
|
||||
/>
|
||||
</div>
|
||||
</FieldFrame>
|
||||
<Dropdown
|
||||
trigger={({ open, toggle, setTriggerRef, surfaceId }) => (
|
||||
<Button ref={setTriggerRef} aria-expanded={open} aria-controls={surfaceId} onClick={toggle}>Действия</Button>
|
||||
@@ -255,10 +357,12 @@ export function CatalogApp() {
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Settings controls" note="new Engine generation">
|
||||
<div className="catalog-form">
|
||||
<Checker checked={checker} label="Автоматическое обновление" description="Применять изменения без перезапуска" onChange={setChecker} />
|
||||
<RangeControl label="Интенсивность стекла" value={range} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setRange} />
|
||||
<Preview title="Environment controls" note="exact Engine geometry">
|
||||
<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} />
|
||||
<Checker checked={checker} label="Использовать цвет портов" onChange={setChecker} />
|
||||
<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">
|
||||
@@ -273,7 +377,7 @@ export function CatalogApp() {
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section id="windows" eyebrow="03 / Layers" title="Окна и подтверждения" description="Modal и side panel используют одну механику portal, Escape, focus trap и focus restore.">
|
||||
<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>
|
||||
@@ -285,9 +389,9 @@ export function CatalogApp() {
|
||||
|
||||
<Section id="inspector" eyebrow="04 / Pattern" title="Inspector / Settings" description="Канон основан только на новом Environment Settings и NDC Agent Inspector, без legacy-инспекторов Engine.">
|
||||
<div className="catalog-inspector-stage">
|
||||
<GlassSurface tone="strong" radius="panel" padding="md" className="catalog-inspector-card">
|
||||
<div className="catalog-inspector-title"><strong>Инспектор</strong><span>Agent Definition</span></div>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["definition"]} activeId="definition" />
|
||||
<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>
|
||||
@@ -312,8 +416,15 @@ export function CatalogApp() {
|
||||
</div>
|
||||
</Window>
|
||||
|
||||
<Window open={inspectorOpen} title="Инспектор" subtitle="Agent Definition" placement="end" onClose={() => setInspectorOpen(false)}>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["definition"]} activeId="definition" />
|
||||
<Window
|
||||
open={inspectorOpen}
|
||||
title="Настройки окружения"
|
||||
subtitle="ENGINE / новый канон"
|
||||
placement="end"
|
||||
style={engineAccentStyle}
|
||||
onClose={() => setInspectorOpen(false)}
|
||||
>
|
||||
<Inspector sections={inspectorSections} defaultOpen={["illumination", "connections", "ports"]} />
|
||||
</Window>
|
||||
|
||||
<ConfirmationModal
|
||||
@@ -328,4 +439,3 @@ export function CatalogApp() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+40
-13
@@ -30,20 +30,16 @@ textarea {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
[data-nodedc-theme="light"] .catalog-app .nodedc-header__brand img {
|
||||
filter: brightness(0.12);
|
||||
.catalog-admin-layer {
|
||||
position: fixed;
|
||||
z-index: 390;
|
||||
top: 6.55rem;
|
||||
bottom: 1.25rem;
|
||||
left: 1.25rem;
|
||||
}
|
||||
|
||||
.catalog-avatar {
|
||||
display: inline-grid;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
place-items: center;
|
||||
border-radius: 999px;
|
||||
background: rgb(var(--nodedc-accent-rgb));
|
||||
color: rgb(var(--nodedc-on-accent-rgb));
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
[data-nodedc-theme="light"] .catalog-app .nodedc-header__brand img {
|
||||
filter: brightness(0.12);
|
||||
}
|
||||
|
||||
.catalog-main {
|
||||
@@ -52,6 +48,13 @@ textarea {
|
||||
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);
|
||||
}
|
||||
|
||||
.catalog-main[data-admin-open="true"] {
|
||||
width: min(1180px, calc(100% - 25rem));
|
||||
margin-right: 1rem;
|
||||
margin-left: 24rem;
|
||||
}
|
||||
|
||||
.catalog-hero {
|
||||
@@ -80,6 +83,10 @@ textarea {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.catalog-section-nav {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.catalog-section {
|
||||
display: grid;
|
||||
scroll-margin-top: 2rem;
|
||||
@@ -205,6 +212,13 @@ textarea {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-engine-control-stack {
|
||||
display: grid;
|
||||
width: min(var(--nodedc-inspector-inner-width), 100%);
|
||||
justify-self: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-surface-stack .nodedc-glass {
|
||||
min-height: 4rem;
|
||||
display: flex;
|
||||
@@ -227,12 +241,13 @@ textarea {
|
||||
|
||||
.catalog-inspector-card {
|
||||
width: min(24.375rem, 100%);
|
||||
padding: 1.125rem 0.75rem;
|
||||
}
|
||||
|
||||
.catalog-inspector-title {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
padding: 0.75rem 0.75rem 1rem;
|
||||
padding: 0 0.875rem 0.875rem;
|
||||
}
|
||||
|
||||
.catalog-inspector-title span {
|
||||
@@ -246,6 +261,11 @@ textarea {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.catalog-main[data-admin-open="true"] {
|
||||
width: min(1180px, calc(100% - 2rem));
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.catalog-grid,
|
||||
.catalog-grid--foundation {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -258,4 +278,11 @@ textarea {
|
||||
.catalog-inspector-stage {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.catalog-admin-layer {
|
||||
top: 0.875rem;
|
||||
right: 0.875rem;
|
||||
bottom: 0.875rem;
|
||||
left: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user