diff --git a/apps/catalog/src/CatalogApp.tsx b/apps/catalog/src/CatalogApp.tsx index 5f4c58e..d54fb17 100644 --- a/apps/catalog/src/CatalogApp.tsx +++ b/apps/catalog/src/CatalogApp.tsx @@ -29,6 +29,8 @@ import { InspectorSelectField, MediaSourceField, RangeControl, + ResourceRow, + ResourceList, SegmentedControl, Select, ShareAccessModal, @@ -1408,6 +1410,12 @@ export function CatalogApp() {

При reduced motion кольцо остаётся видимым без вращения; процесс и его завершение принадлежат приложению.

+ + +
  • } title="Сохранённый результат" metadata="Сегодня · доступен для просмотра" actions={ setProjectName("Сохранённый результат")}>} />
  • +
  • } title="Получение сведений" metadata="Действие выполняется" aria-busy="true" actions={} />
  • +
    +
    @@ -1992,6 +2000,7 @@ export function CatalogApp() { endPanel={
    } header={ } brandHref="/" center={ diff --git a/docs/APPLICATION_TEMPLATE.md b/docs/APPLICATION_TEMPLATE.md index ddc02d2..f9d0e59 100644 --- a/docs/APPLICATION_TEMPLATE.md +++ b/docs/APPLICATION_TEMPLATE.md @@ -44,3 +44,7 @@ Modal остаётся portal-слоем. Его тело прокручивае - предметное содержимое content window. Shell владеет геометрией, слоями, переходами и breakpoint-поведением. См. готовый путь подключения в `docs/CONSUMPTION.md`. + +На узком экране скрывается именно действие `data-action="expand"`. +Порядок utility actions не является признаком типа действия: плюс и обновление +остаются доступны, даже когда стоят первыми в шапке. Исправлено при QA Node 0.3. diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md index d912f61..5e19703 100644 --- a/docs/COMPONENTS.md +++ b/docs/COMPONENTS.md @@ -312,3 +312,12 @@ Inspector владеет: Для Engine Environment Settings desktop-контракт точный: окно `390 px`, рабочая колонка `330 px`, строка `154 + 14 + 162 px`, высота контрола `46 px`, section header `50 px` с радиусом `12 px`. Accent-filled section headers и полноширинные range/checker/select сохраняют геометрию исходника. Engine продолжает владеть определениями полей, типами нод и сохранением значений. В живом catalog эти collapsible sections находятся в `Guideline → Контролы`; отдельной product-вкладки Inspector нет. + +## ResourceRow и ResourceList + +Общие компактные строки ресурсов, выделенные из Mission Core AI Inference. +Контракт, состояния и происхождение: [RESOURCE_ROW.md](RESOURCE_ROW.md). + +Светлый одноцветный знак шапки явно помечается `AppHeader.brandMonochrome` и +`HeaderWorkspace.monochrome`: общая тема обеспечивает контраст на светлом фоне. +По умолчанию флаг выключен; цветные изображения и аватары не меняются. diff --git a/docs/RESOURCE_ROW.md b/docs/RESOURCE_ROW.md new file mode 100644 index 0000000..89fff3c --- /dev/null +++ b/docs/RESOURCE_ROW.md @@ -0,0 +1,23 @@ +# ResourceRow и ResourceList + +Владелец 2026-09-05 запросил перенос существующих длинных строк Mission Core +AI Inference в Node с повторным использованием Design Guideline. Источник: +ObservatoryWorkspace / observatory-evidence-card. Это выделение существующего +оформления в общий компонент, без лабораторной модели данных. + +`ResourceList` содержит обычные `li`; внутри — `ResourceRow` с обязательным +`title`, опциональными `icon`, `description`, `metadata`, `status`, `actions`. +Слоты действий принимают канонические Button/IconButton. Клик по всей строке +не назначается: самостоятельные действия имеют свои доступные подписи и focus. + +Геометрия источника сохранена: минимум 4.25rem, отступы .7/.85rem, значок +2.15rem, компактный радиус и существующие токены поверхности/типографики. +Длинное название переносится; метаданные сокращаются визуально, полный текст +можно передать через title. На узком экране статус и действия переходят ниже. + +Состояния поставляет consumer: нормальное, пустой список, загрузка, ошибка, +недоступное действие. Pending отмечается aria-busy и ActivityIndicator; +disabled и keyboard/focus остаются у общего контрола. Строка не объявляет +объект доступным или подключённым самостоятельно. + +Пример находится в живом каталоге, раздел «Контролы → Строки ресурсов». diff --git a/packages/ui-core/styles.css b/packages/ui-core/styles.css index f086aa6..28e17cb 100644 --- a/packages/ui-core/styles.css +++ b/packages/ui-core/styles.css @@ -3680,7 +3680,7 @@ textarea.nodedc-field__control { display: none; } - .nodedc-application-panel__action:first-child { + .nodedc-application-panel__action[data-action="expand"] { display: none; } @@ -3777,3 +3777,22 @@ textarea.nodedc-field__control { transition: none; } } + +/* ResourceRow: admitted extraction of Mission Core AI Inference list geometry. */ +.nodedc-resource-list { display: grid; gap: .4rem; margin: 0; padding: 0; list-style: none; } +.nodedc-resource-row { display: flex; align-items: center; gap: .8rem; min-height: 4.25rem; padding: .7rem .85rem; border-radius: var(--nodedc-radius-control-compact); background: var(--nodedc-glass-control-bg); } +.nodedc-resource-row__icon { display: grid; flex: 0 0 2.15rem; height: 2.15rem; place-items: center; border-radius: var(--nodedc-radius-circle); background: var(--nodedc-panel-icon-bg); color: var(--nodedc-text-secondary); } +.nodedc-resource-row__copy { display: grid; flex: 1; min-width: 0; gap: .16rem; } +.nodedc-resource-row__copy strong { font-size: var(--nodedc-font-size-sm); line-height: 1.3; overflow-wrap: anywhere; } +.nodedc-resource-row__copy > span, .nodedc-resource-row__copy > small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.nodedc-resource-row__copy > span { color: var(--nodedc-text-secondary); font-size: var(--nodedc-font-size-sm); } +.nodedc-resource-row__copy > small { color: var(--nodedc-text-muted); font-size: var(--nodedc-font-size-xs); } +.nodedc-resource-row__actions { display: flex; align-items: center; gap: .85rem; } +.nodedc-resource-row__status { font-size: var(--nodedc-font-size-sm); } +@media (max-width: 540px) { + .nodedc-resource-row { flex-wrap: wrap; } + .nodedc-resource-row__copy { flex-basis: calc(100% - 3rem); } + .nodedc-resource-row__actions { margin-left: auto; } +} + +[data-nodedc-theme="light"] :is(.nodedc-header__brand, .nodedc-header__workspace)[data-monochrome="true"] img { filter: brightness(0); } diff --git a/packages/ui-react/src/AppHeader.tsx b/packages/ui-react/src/AppHeader.tsx index 88c3df8..5071f6b 100644 --- a/packages/ui-react/src/AppHeader.tsx +++ b/packages/ui-react/src/AppHeader.tsx @@ -4,6 +4,8 @@ export interface AppHeaderProps { brand: ReactNode; brandHref?: string; brandLabel?: string; + /** Opt in only for a light, single-color mark, never a full-color image. */ + brandMonochrome?: boolean; left?: ReactNode; center?: ReactNode; right?: ReactNode; @@ -13,14 +15,15 @@ export function AppHeader({ brand, brandHref, brandLabel = "NODE.DC", + brandMonochrome = false, left, center, right, }: AppHeaderProps) { const brandNode = brandHref ? ( - {brand} + {brand} ) : ( - {brand} + {brand} ); return ( @@ -100,11 +103,12 @@ export interface HeaderWorkspaceProps { label: string; imageUrl?: string; kind?: "mark" | "avatar"; + monochrome?: boolean; } -export function HeaderWorkspace({ label, imageUrl, kind = "mark" }: HeaderWorkspaceProps) { +export function HeaderWorkspace({ label, imageUrl, kind = "mark", monochrome = false }: HeaderWorkspaceProps) { return ( - + {imageUrl ? : label.slice(0, 2).toUpperCase()} ); diff --git a/packages/ui-react/src/ResourceRow.tsx b/packages/ui-react/src/ResourceRow.tsx new file mode 100644 index 0000000..3af99e0 --- /dev/null +++ b/packages/ui-react/src/ResourceRow.tsx @@ -0,0 +1,30 @@ +import type { HTMLAttributes, ReactNode } from "react"; +import { cn } from "./cn.js"; + +export interface ResourceRowProps extends Omit, "title"> { + title: ReactNode; + icon?: ReactNode; + description?: ReactNode; + metadata?: ReactNode; + status?: ReactNode; + actions?: ReactNode; +} + +/** Compact resource presentation extracted from Mission Core AI Inference. + * Actions stay canonical controls; the row itself is not a nested button. */ +export function ResourceRow({ title, icon, description, metadata, status, actions, className, ...props }: ResourceRowProps) { + return
    + {icon ? : null} +
    + {title} + {description ? {description} : null} + {metadata ? {metadata} : null} +
    + {status ?
    {status}
    : null} + {actions ?
    {actions}
    : null} +
    ; +} + +export function ResourceList({ className, children, ...props }: HTMLAttributes) { + return
      {children}
    ; +} diff --git a/packages/ui-react/src/index.ts b/packages/ui-react/src/index.ts index 9111d09..e36097a 100644 --- a/packages/ui-react/src/index.ts +++ b/packages/ui-react/src/index.ts @@ -16,6 +16,7 @@ export * from "./Inspector.js"; export * from "./Icon.js"; export * from "./MediaSourceField.js"; export * from "./RangeControl.js"; +export * from "./ResourceRow.js"; export * from "./SegmentedControl.js"; export * from "./Select.js"; export * from "./StatusBadge.js"; diff --git a/registry/components.json b/registry/components.json index 66dbe91..f2c6f1f 100644 --- a/registry/components.json +++ b/registry/components.json @@ -1,6 +1,30 @@ { "schemaVersion": "1.0.0", "components": [ + { + "id": "resource-row", + "status": "baseline", + "package": "@nodedc/ui-react", + "exports": [ + "ResourceRow", + "ResourceList" + ], + "domContract": [ + "nodedc-resource-row", + "nodedc-resource-list" + ], + "summary": "Mission Core AI Inference compact rows extracted for shared resource lists.", + "behavior": [ + "wrapping title", + "optional metadata and status", + "canonical action controls", + "responsive action placement" + ], + "rules": [ + "The consumer supplies identity and states; the row owns presentation only.", + "Use ordinary list items and canonical buttons; do not nest actions inside a clickable row." + ] + }, { "id": "glass-surface", "status": "baseline", @@ -254,6 +278,7 @@ "anatomy": ["left brand", "optional left context", "center navigation/workspace", "right actions/profile"], "rules": [ "The logo occupies the same visual position and geometry across applications.", + "Light single-color assets may opt into brandMonochrome / monochrome for theme contrast; full-color images are unchanged by default.", "Product routes and profile content are application data passed into stable slots.", "The canonical application header is fixed and preserves the Launcher three-axis logo / navigation / profile positions.", "Consumers supply data and actions but cannot override preset geometry through local className or style props."