Align shell actions and icons with Launcher and SEO
This commit is contained in:
parent
1fc39dfebc
commit
8ca12f2676
|
|
@ -529,12 +529,10 @@ export function CatalogApp() {
|
|||
<GlassSurface tone="strong" radius="panel" className="catalog-environment-card">
|
||||
<div className="catalog-environment-title">
|
||||
<div><strong>Настройки окружения</strong><span>ENGINE / новый канон</span></div>
|
||||
<IconButton label="Сохранить текущий layout" onClick={saveEnvironmentLayout}><Icon name="save" /></IconButton>
|
||||
</div>
|
||||
{environmentLayoutSaved ? <p className="catalog-environment-draft" aria-live="polite">Layout сохранён локально. Сервер и MCP не используются.</p> : null}
|
||||
<Inspector sections={environmentSections} defaultOpen={["application-material"]} />
|
||||
</GlassSurface>
|
||||
<p className="catalog-preview__explanation">Environment controls — единый референс Engine: palette, range, checker, split-select и раскрывающиеся sections. Верхняя секция меняет цвет плашек и полей приложения; сохраняется весь текущий layout одной кнопкой сверху.</p>
|
||||
<p className="catalog-preview__explanation">Environment controls — единый референс Engine: palette, range, checker, split-select и раскрывающиеся sections. Верхняя секция меняет цвет плашек и полей приложения; весь layout сохраняется общей кнопкой в шапке окна.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
<Preview title="Статусы операций" note="не цвет приложения" className="catalog-preview--wide">
|
||||
|
|
@ -705,7 +703,7 @@ export function CatalogApp() {
|
|||
}
|
||||
right={
|
||||
<HeaderProfile>
|
||||
<IconButton label="Уведомления"><Icon name="inbox" /></IconButton>
|
||||
<IconButton label="Уведомления"><Icon name="inbox" size={20} strokeWidth={1.7} /></IconButton>
|
||||
<HeaderProfileButton>Профиль</HeaderProfileButton>
|
||||
<HeaderAvatar label="DC" />
|
||||
</HeaderProfile>
|
||||
|
|
@ -755,6 +753,11 @@ export function CatalogApp() {
|
|||
description={activeDefinition.description}
|
||||
expanded={panelExpanded}
|
||||
onExpandedChange={workspace.setContentExpanded}
|
||||
utilityActions={[{
|
||||
label: environmentLayoutSaved ? "Layout сохранён локально" : "Сохранить layout",
|
||||
icon: "save",
|
||||
onClick: saveEnvironmentLayout,
|
||||
}]}
|
||||
onClose={workspace.closeView}
|
||||
>
|
||||
<div className="catalog-panel-content">{renderSectionContent()}</div>
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@
|
|||
2. `stage` — главное медиа- или предметное окно приложения.
|
||||
3. `navigation` — левая панель шириной `clamp(332px, 19.5vw, 352px)`.
|
||||
4. `content` — отдельное рабочее окно справа от навигации.
|
||||
5. `ApplicationPanel` — заголовок, действия expand/close и независимо прокручиваемое тело контента.
|
||||
5. `ApplicationPanel` — заголовок, постоянные utility actions, действия expand/close и независимо прокручиваемое тело контента.
|
||||
|
||||
Позиции шапки не пересчитываются под конкретное приложение. Меняются подписи, доступные navigation actions, профиль и содержимое stage. `HeaderNavigation`, `HeaderWorkspace`, `HeaderProfile`, `HeaderProfileButton` и `HeaderAvatar` должны использоваться как готовые части пресета; consumer не назначает им собственные размеры и offsets.
|
||||
|
||||
Действие, относящееся ко всему рабочему окну, передаётся через `ApplicationPanel.utilityActions`. Например, сохранение настроенного layout всегда находится в шапке перед expand/close и остаётся доступным при переходе между разделами; отдельные карточки не рисуют собственные копии этой кнопки.
|
||||
|
||||
## Состояния desktop
|
||||
|
||||
| Состояние | Навигация | Content window | Stage |
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ Pill navigation для верхней панели и компактного п
|
|||
|
||||
`useApplicationWorkspace` является каноническим контроллером этих состояний для React. Приложение не должно заново связывать набор локальных boolean-state для navigation/content/expand. DOM-проекты используют идентичный `createApplicationWorkspaceController`.
|
||||
|
||||
Постоянные действия всего рабочего окна передаются через `ApplicationPanel.utilityActions` и всегда находятся в правой части шапки перед expand/close. Сохранение общего layout или настроек приложения — одно такое действие для всего окна; оно не дублируется внутри отдельных карточек и секций.
|
||||
|
||||
Точные состояния и размеры зафиксированы в `docs/APPLICATION_TEMPLATE.md`.
|
||||
|
||||
## MediaSourceField
|
||||
|
|
@ -142,7 +144,7 @@ Pill navigation для верхней панели и компактного п
|
|||
|
||||
## Icon
|
||||
|
||||
`Icon` предоставляет только подтверждённый общий subset иконок. Каноническое имя описывает смысл (`close`, `expand`, `refresh`), а не конкретный путь SVG. Default glyph — `16 px`, stroke — `1.6`; внутри кнопок glyph оптически уменьшается до `78%`, но размеры surface/hit target остаются прежними.
|
||||
`Icon` предоставляет только подтверждённый общий subset иконок. Каноническое имя описывает смысл (`close`, `expand`, `refresh`), а не конкретный путь SVG. Default glyph — `16 px`, stroke — `1.6`; размер не уменьшается дополнительным CSS-scale. В profile-group шапки допустим подтверждённый Launcher-размер `20 px`. Размеры surface/hit target при этом остаются независимыми и не уменьшаются.
|
||||
|
||||
Поверхность, круглая форма, hit target, active и disabled состояния принадлежат `IconButton`, `Button` или navigation item. Полный список находится в `registry/icons.json` и `docs/ICONS.md`.
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,14 @@
|
|||
--nodedc-app-panel-shadow-strong: 0 34px 110px rgba(0, 0, 0, 0.52);
|
||||
--nodedc-panel-action-border: rgba(255, 255, 255, 0.22);
|
||||
--nodedc-panel-action-border-hover: rgba(255, 255, 255, 0.3);
|
||||
--nodedc-panel-action-bg: transparent;
|
||||
--nodedc-panel-action-shadow: none;
|
||||
--nodedc-panel-item-bg: rgba(255, 255, 255, 0.04);
|
||||
--nodedc-panel-item-hover-bg: rgba(255, 255, 255, 0.085);
|
||||
--nodedc-panel-item-active-bg: rgba(255, 255, 255, 0.115);
|
||||
--nodedc-panel-icon-bg: rgba(255, 255, 255, 0.045);
|
||||
--nodedc-panel-active-icon-bg: rgba(247, 248, 244, 0.96);
|
||||
--nodedc-panel-active-icon-color: rgb(var(--nodedc-on-accent-rgb));
|
||||
--nodedc-panel-active-icon-color: rgba(8, 8, 10, 0.96);
|
||||
--nodedc-scrollbar-thumb: rgba(255, 255, 255, 0.15);
|
||||
--nodedc-primary-action-bg: rgb(var(--nodedc-accent-rgb));
|
||||
--nodedc-primary-action-hover: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 84%, white);
|
||||
|
|
@ -105,6 +107,8 @@
|
|||
--nodedc-app-panel-shadow-strong: 0 18px 44px rgba(0, 0, 0, 0.14);
|
||||
--nodedc-panel-action-border: rgba(24, 32, 29, 0.12);
|
||||
--nodedc-panel-action-border-hover: rgba(24, 32, 29, 0.22);
|
||||
--nodedc-panel-action-bg: #ffffff;
|
||||
--nodedc-panel-action-shadow: 0 10px 24px rgba(24, 32, 29, 0.08);
|
||||
--nodedc-panel-item-bg: #f4f4f4;
|
||||
--nodedc-panel-item-hover-bg: #eeeeee;
|
||||
--nodedc-panel-item-active-bg: #e8e8e8;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
--nodedc-inspector-button-radius: 1rem;
|
||||
--nodedc-select-toggle-width: 2.875rem;
|
||||
--nodedc-select-gap: 0.5rem;
|
||||
--nodedc-icon-glyph-scale: 0.78;
|
||||
--nodedc-icon-glyph-scale: 1;
|
||||
|
||||
--nodedc-space-1: 0.25rem;
|
||||
--nodedc-space-2: 0.5rem;
|
||||
|
|
|
|||
|
|
@ -1041,6 +1041,11 @@ textarea.nodedc-field__control {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.nodedc-header__profile .nodedc-icon-button > svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.nodedc-header__profile-button {
|
||||
display: inline-flex;
|
||||
min-height: var(--nodedc-header-control-height);
|
||||
|
|
@ -1307,8 +1312,9 @@ textarea.nodedc-field__control {
|
|||
border: 1px solid var(--nodedc-panel-action-border);
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
background: var(--nodedc-panel-action-bg);
|
||||
color: var(--nodedc-text-secondary);
|
||||
box-shadow: var(--nodedc-panel-action-shadow);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -1319,7 +1325,7 @@ textarea.nodedc-field__control {
|
|||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-application-panel[data-expanded="true"] .nodedc-application-panel__action:first-child {
|
||||
.nodedc-application-panel__action[data-active="true"] {
|
||||
border-color: transparent;
|
||||
background: var(--nodedc-glass-control-active);
|
||||
color: var(--nodedc-glass-control-active-text);
|
||||
|
|
@ -1598,6 +1604,10 @@ textarea.nodedc-field__control {
|
|||
color: var(--nodedc-panel-active-icon-color);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item[data-active="true"] .nodedc-admin-panel__nav-icon > svg {
|
||||
color: var(--nodedc-panel-active-icon-color);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__footer {
|
||||
width: calc(100% + 2.2rem);
|
||||
margin-inline: -1.1rem;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { HTMLAttributes, ReactNode } from "react";
|
||||
import { cn } from "./cn.js";
|
||||
import { Icon } from "./Icon.js";
|
||||
import { Icon, type IconName } from "./Icon.js";
|
||||
|
||||
export interface ApplicationShellProps extends Omit<HTMLAttributes<HTMLDivElement>, "content"> {
|
||||
header: ReactNode;
|
||||
|
|
@ -49,10 +49,19 @@ export interface ApplicationPanelProps extends Omit<HTMLAttributes<HTMLElement>,
|
|||
expandLabel?: string;
|
||||
closeLabel?: string;
|
||||
onExpandedChange?: (expanded: boolean) => void;
|
||||
/** Persistent utility actions rendered before expand/close in the panel header. */
|
||||
utilityActions?: ApplicationPanelUtilityAction[];
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export interface ApplicationPanelUtilityAction {
|
||||
label: string;
|
||||
icon: IconName;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function ApplicationPanel({
|
||||
eyebrow,
|
||||
title,
|
||||
|
|
@ -61,6 +70,7 @@ export function ApplicationPanel({
|
|||
expandLabel = "Развернуть окно",
|
||||
closeLabel = "Закрыть окно",
|
||||
onExpandedChange,
|
||||
utilityActions = [],
|
||||
onClose,
|
||||
children,
|
||||
className,
|
||||
|
|
@ -75,18 +85,33 @@ export function ApplicationPanel({
|
|||
{description ? <p>{description}</p> : null}
|
||||
</div>
|
||||
<div className="nodedc-application-panel__actions">
|
||||
{utilityActions.map((action) => (
|
||||
<button
|
||||
key={action.label}
|
||||
type="button"
|
||||
className="nodedc-application-panel__action"
|
||||
data-action="utility"
|
||||
aria-label={action.label}
|
||||
disabled={action.disabled}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<Icon name={action.icon} size={16} strokeWidth={1.6} />
|
||||
</button>
|
||||
))}
|
||||
{onExpandedChange ? (
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-application-panel__action"
|
||||
data-action="expand"
|
||||
data-active={expanded ? "true" : undefined}
|
||||
aria-label={expanded ? "Свернуть окно" : expandLabel}
|
||||
onClick={() => onExpandedChange(!expanded)}
|
||||
>
|
||||
<Icon name={expanded ? "minimize" : "expand"} />
|
||||
<Icon name={expanded ? "minimize" : "expand"} size={16} strokeWidth={1.6} />
|
||||
</button>
|
||||
) : null}
|
||||
<button type="button" className="nodedc-application-panel__action" aria-label={closeLabel} onClick={onClose}>
|
||||
<Icon name="close" />
|
||||
<button type="button" className="nodedc-application-panel__action" data-action="close" aria-label={closeLabel} onClick={onClose}>
|
||||
<Icon name="close" size={16} strokeWidth={1.6} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -196,13 +196,14 @@
|
|||
"domExports": ["createApplicationWorkspaceController"],
|
||||
"domContract": ["nodedc-app-shell", "nodedc-application-panel"],
|
||||
"summary": "Launcher-derived application template with fixed header, media stage, left navigation and a separate expandable content window.",
|
||||
"anatomy": ["fixed AppHeader", "stage", "left navigation", "right content window", "expand and close actions"],
|
||||
"behavior": ["one shared navigation/content state machine", "navigation shifts stage", "content opens beside navigation", "expanded content replaces stage", "mobile panels use the available viewport sequentially", "independent content scrolling"],
|
||||
"anatomy": ["fixed AppHeader", "stage", "left navigation", "right content window", "persistent utility actions", "expand and close actions"],
|
||||
"behavior": ["one shared navigation/content state machine", "navigation shifts stage", "content opens beside navigation", "expanded content replaces stage", "global utility actions remain in the panel header across sections", "mobile panels use the available viewport sequentially", "independent content scrolling"],
|
||||
"rules": [
|
||||
"The bottom Launcher service rail is product-specific and is not part of the general application template.",
|
||||
"Desktop page gap is 20 px and navigation width follows the Launcher 332–352 px range.",
|
||||
"Applications supply routes, profile data, stage media and panel content without changing shell geometry.",
|
||||
"Light panels use the SEO white-glass surface tokens rather than a dark shell with inverted text."
|
||||
"Light panels use the SEO white surface and shadow tokens rather than a dark shell with inverted text.",
|
||||
"A layout-level save action belongs to ApplicationPanel.utilityActions and is never duplicated inside individual content cards."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue