feat(ui): support leading resource status indicators

This commit is contained in:
Codex
2026-09-06 01:17:37 +03:00
parent 3adeb33b1b
commit 999864e5b0
5 changed files with 17 additions and 3 deletions
+1
View File
@@ -1421,6 +1421,7 @@ export function CatalogApp() {
<Preview title="Строки ресурсов" note="Mission Core / общий список"> <Preview title="Строки ресурсов" note="Mission Core / общий список">
<ResourceList aria-label="Пример списка ресурсов"> <ResourceList aria-label="Пример списка ресурсов">
<li><ResourceRow icon={<Icon name="file" />} title="Сохранённый результат" metadata="Сегодня · доступен для просмотра" actions={<IconButton label="Просмотреть пример" onClick={() => setProjectName("Сохранённый результат")}><Icon name="eye" /></IconButton>} /></li> <li><ResourceRow icon={<Icon name="file" />} title="Сохранённый результат" metadata="Сегодня · доступен для просмотра" actions={<IconButton label="Просмотреть пример" onClick={() => setProjectName("Сохранённый результат")}><Icon name="eye" /></IconButton>} /></li>
<li><ResourceRow icon={<Icon name="camera" />} title="Подключённая камера" description="Индикатор перед названием" statusPlacement="leading" status={<StatusBadge variant="indicator" tone="success" aria-label="Подключено" title="Подключено" />} actions={<IconButton label="Просмотреть камеру" onClick={() => setProjectName("Подключённая камера")}><Icon name="eye" /></IconButton>} /></li>
<li><ResourceRow icon={<Icon name="camera" />} title="Подготовка камеры" metadata="Проверка потоков" progress={{label:"Подготовка камеры",value:0.8}} aria-busy="true" actions={<IconButton label="Просмотр недоступен" disabled><Icon name="eye" /></IconButton>} /></li> <li><ResourceRow icon={<Icon name="camera" />} title="Подготовка камеры" metadata="Проверка потоков" progress={{label:"Подготовка камеры",value:0.8}} aria-busy="true" actions={<IconButton label="Просмотр недоступен" disabled><Icon name="eye" /></IconButton>} /></li>
</ResourceList> </ResourceList>
<SettingsCard title="Устройства не обнаружены" description="Подключите устройство, чтобы оно появилось в списке." /> <SettingsCard title="Устройства не обнаружены" description="Подключите устройство, чтобы оно появилось в списке." />
+7
View File
@@ -25,3 +25,10 @@ disabled и keyboard/focus остаются у общего контрола. С
Для длительных операций с линейной индикацией используйте `progress` Для длительных операций с линейной индикацией используйте `progress`
(ProgressBar: label, value, valueText). Он заменяет status, оставляет действия (ProgressBar: label, value, valueText). Он заменяет status, оставляет действия
справа и занимает промежуток после названия. справа и занимает промежуток после названия.
`statusPlacement="leading"` размещает компактный индикатор после иконки и
перед всем текстовым блоком, с выравниванием по вертикальному центру строки.
По умолчанию остаётся `trailing` перед действиями. Статус не дублируется;
во время `progress` он скрыт в любом положении. Для одной лампы используйте
StatusBadge variant="indicator" с aria-label и title. Это согласованное
06.09.2026 расположение индикатора устройств Mission Core / Node.
+2
View File
@@ -3790,9 +3790,11 @@ textarea.nodedc-field__control {
.nodedc-resource-row__copy > small { color: var(--nodedc-text-muted); font-size: var(--nodedc-font-size-xs); } .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__actions { display: flex; align-items: center; gap: .85rem; }
.nodedc-resource-row__status { font-size: var(--nodedc-font-size-sm); } .nodedc-resource-row__status { font-size: var(--nodedc-font-size-sm); }
.nodedc-resource-row--leading-status > .nodedc-resource-row__status { display: flex; align-items: center; flex: 0 0 auto; }
@media (max-width: 540px) { @media (max-width: 540px) {
.nodedc-resource-row { flex-wrap: wrap; } .nodedc-resource-row { flex-wrap: wrap; }
.nodedc-resource-row__copy { flex-basis: calc(100% - 3rem); } .nodedc-resource-row__copy { flex-basis: calc(100% - 3rem); }
.nodedc-resource-row--leading-status > .nodedc-resource-row__copy { flex: 1 1 0; }
.nodedc-resource-row__actions { margin-left: auto; } .nodedc-resource-row__actions { margin-left: auto; }
} }
+6 -3
View File
@@ -8,22 +8,25 @@ export interface ResourceRowProps extends Omit<HTMLAttributes<HTMLDivElement>, "
description?: ReactNode; description?: ReactNode;
metadata?: ReactNode; metadata?: ReactNode;
status?: ReactNode; status?: ReactNode;
statusPlacement?: "leading" | "trailing";
actions?: ReactNode; actions?: ReactNode;
progress?: Pick<ProgressBarProps, "label" | "value" | "valueText">; progress?: Pick<ProgressBarProps, "label" | "value" | "valueText">;
} }
/** Compact resource presentation extracted from Mission Core AI Inference. /** Compact resource presentation extracted from Mission Core AI Inference.
* Actions stay canonical controls; the row itself is not a nested button. */ * Actions stay canonical controls; the row itself is not a nested button. */
export function ResourceRow({ title, icon, description, metadata, status, actions, progress, className, ...props }: ResourceRowProps) { export function ResourceRow({ title, icon, description, metadata, status, statusPlacement = "trailing", actions, progress, className, ...props }: ResourceRowProps) {
return <div className={cn("nodedc-resource-row", progress && "nodedc-resource-row--progress", className)} {...props}> const leadingStatus = !progress && !!status && statusPlacement === "leading";
return <div className={cn("nodedc-resource-row", progress && "nodedc-resource-row--progress", leadingStatus && "nodedc-resource-row--leading-status", className)} {...props}>
{icon ? <span className="nodedc-resource-row__icon" aria-hidden="true">{icon}</span> : null} {icon ? <span className="nodedc-resource-row__icon" aria-hidden="true">{icon}</span> : null}
{leadingStatus ? <div className="nodedc-resource-row__status">{status}</div> : null}
<div className="nodedc-resource-row__copy"> <div className="nodedc-resource-row__copy">
<strong>{title}</strong> <strong>{title}</strong>
{description ? <span>{description}</span> : null} {description ? <span>{description}</span> : null}
{metadata ? <small>{metadata}</small> : null} {metadata ? <small>{metadata}</small> : null}
</div> </div>
{progress ? <ProgressBar {...progress} /> : null} {progress ? <ProgressBar {...progress} /> : null}
{!progress && status ? <div className="nodedc-resource-row__status">{status}</div> : null} {!progress && status && statusPlacement === "trailing" ? <div className="nodedc-resource-row__status">{status}</div> : null}
{actions ? <div className="nodedc-resource-row__actions">{actions}</div> : null} {actions ? <div className="nodedc-resource-row__actions">{actions}</div> : null}
</div>; </div>;
} }
+1
View File
@@ -39,6 +39,7 @@
"behavior": [ "behavior": [
"wrapping title", "wrapping title",
"optional metadata and status", "optional metadata and status",
"optional leading status between icon and copy, vertically centered",
"canonical action controls", "canonical action controls",
"responsive action placement" "responsive action placement"
], ],