feat(ui): add bounded workspace windows

This commit is contained in:
Codex
2026-07-16 20:56:23 +03:00
parent a1c4ecaa93
commit 5f583caa05
8 changed files with 661 additions and 3 deletions
+44
View File
@@ -43,9 +43,11 @@ import {
useApplicationWorkspace,
Window,
WindowFooterActions,
WorkspaceWindow,
type IconName,
type ShareAccessMember,
type ToolbarPlacement,
type WorkspaceWindowRect,
} from "@nodedc/ui-react";
import { applyFaviconAssets, createIcoBlob, generateFavicons, type FaviconAssetUrls, type GeneratedFavicon } from "./favicon.js";
import {
@@ -371,6 +373,10 @@ export function CatalogApp() {
const [strokeColor, setStrokeColor] = useState("#2b2b36");
const [strokeOpacity, setStrokeOpacity] = useState(100);
const [inspectorOpen, setInspectorOpen] = useState(false);
const workspaceWindowDemoRef = useRef<HTMLDivElement>(null);
const [workspaceWindowDemoOpen, setWorkspaceWindowDemoOpen] = useState(true);
const [workspaceWindowDemoMaximized, setWorkspaceWindowDemoMaximized] = useState(false);
const [workspaceWindowDemoRect, setWorkspaceWindowDemoRect] = useState<WorkspaceWindowRect>({ x: 24, y: 24, width: 340, height: 230 });
const [createModalOpen, setCreateModalOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [shareAccessOpen, setShareAccessOpen] = useState(false);
@@ -1296,6 +1302,44 @@ export function CatalogApp() {
<IconButton label="Закрыть"><Icon name="close" /></IconButton>
</div>
</Preview>
<Preview title="Workspace window" note="inline / bounded / controlled" className="catalog-preview--wide catalog-workspace-window-preview">
<div ref={workspaceWindowDemoRef} className="catalog-workspace-window-demo">
{workspaceWindowDemoOpen ? (
<WorkspaceWindow
boundsRef={workspaceWindowDemoRef}
rect={workspaceWindowDemoRect}
onRectChange={setWorkspaceWindowDemoRect}
maximized={workspaceWindowDemoMaximized}
onMaximizedChange={setWorkspaceWindowDemoMaximized}
onActivate={() => undefined}
onClose={() => {
setWorkspaceWindowDemoOpen(false);
setWorkspaceWindowDemoMaximized(false);
}}
title="Визуальный канал"
subtitle="WORKSPACE / AUXILIARY VIEW"
status="LIVE"
footer={<span className="catalog-workspace-window-demo__hint">Стрелки позиция · Shift + стрелки 10 px</span>}
active
zIndex={2}
>
<div className="catalog-workspace-window-demo__media">
<Icon name="video" size={24} />
<strong>Рабочая поверхность</strong>
<span>Перемещается, изменяет размер и разворачивается только внутри сцены.</span>
</div>
</WorkspaceWindow>
) : (
<Button
className="catalog-workspace-window-demo__reopen"
shape="pill"
icon={<Icon name="plus" />}
onClick={() => setWorkspaceWindowDemoOpen(true)}
>Открыть окно</Button>
)}
</div>
<p className="catalog-preview__explanation">Rectangle, maximize, close и stacking остаются состоянием приложения; дизайн-система владеет одинаковой bounded-геометрией и доступным управлением.</p>
</Preview>
<Preview title="Inspector" note="modeless / draggable" className="catalog-preview--wide catalog-preview--compact catalog-inspector-launcher">
<p className="catalog-preview__explanation">Настройки приложения и Toolbar открываются в отдельном перемещаемом Inspector и сохраняются одним серверным layout.</p>
<Button variant="primary" shape="pill" icon={<Icon name="panel" />} onClick={() => setInspectorOpen(true)}>Открыть Inspector</Button>
+59
View File
@@ -897,6 +897,65 @@ textarea {
grid-column: 1 / -1;
}
.catalog-workspace-window-preview .catalog-preview__body {
align-content: stretch;
}
.catalog-workspace-window-demo {
position: relative;
min-height: 24rem;
overflow: hidden;
border-radius: var(--nodedc-radius-card);
background:
linear-gradient(color-mix(in srgb, var(--nodedc-text-primary) 5%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--nodedc-text-primary) 5%, transparent) 1px, transparent 1px),
color-mix(in srgb, var(--nodedc-canvas) 88%, var(--nodedc-glass-panel-bg-soft));
background-size: 2.5rem 2.5rem;
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--nodedc-text-primary) 8%, transparent);
}
.catalog-workspace-window-demo .nodedc-workspace-window__body {
display: grid;
padding: 0.25rem 0.75rem 0.5rem;
}
.catalog-workspace-window-demo__media {
display: grid;
min-height: 0;
place-content: center;
justify-items: center;
gap: 0.45rem;
border-radius: calc(var(--nodedc-radius-card) * 0.72);
background:
radial-gradient(circle at 62% 30%, color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 18%, transparent), transparent 38%),
color-mix(in srgb, var(--nodedc-canvas) 72%, transparent);
color: var(--nodedc-text-secondary);
text-align: center;
}
.catalog-workspace-window-demo__media strong {
color: var(--nodedc-text-primary);
font-size: 0.82rem;
}
.catalog-workspace-window-demo__media span,
.catalog-workspace-window-demo__hint {
color: var(--nodedc-text-muted);
font-size: 0.66rem;
line-height: 1.4;
}
.catalog-workspace-window-demo__media span {
max-width: 16rem;
}
.catalog-workspace-window-demo__reopen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.catalog-inspector-launcher .nodedc-button {
width: 100%;
}