feat(ui): share resource rows and refine Node shell behavior

This commit is contained in:
Codex
2026-09-05 17:25:27 +03:00
parent d51d8bb7f6
commit 8a79dfe84d
9 changed files with 129 additions and 5 deletions
+20 -1
View File
@@ -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); }
+8 -4
View File
@@ -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 ? (
<a className="nodedc-header__brand" href={brandHref} aria-label={brandLabel}>{brand}</a>
<a className="nodedc-header__brand" data-monochrome={brandMonochrome || undefined} href={brandHref} aria-label={brandLabel}>{brand}</a>
) : (
<span className="nodedc-header__brand" aria-label={brandLabel}>{brand}</span>
<span className="nodedc-header__brand" data-monochrome={brandMonochrome || undefined} aria-label={brandLabel}>{brand}</span>
);
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 (
<span className="nodedc-header__workspace" data-kind={kind} title={label}>
<span className="nodedc-header__workspace" data-kind={kind} data-monochrome={monochrome || undefined} title={label}>
{imageUrl ? <img src={imageUrl} alt="" /> : label.slice(0, 2).toUpperCase()}
</span>
);
+30
View File
@@ -0,0 +1,30 @@
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "./cn.js";
export interface ResourceRowProps extends Omit<HTMLAttributes<HTMLDivElement>, "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 <div className={cn("nodedc-resource-row", className)} {...props}>
{icon ? <span className="nodedc-resource-row__icon" aria-hidden="true">{icon}</span> : null}
<div className="nodedc-resource-row__copy">
<strong>{title}</strong>
{description ? <span>{description}</span> : null}
{metadata ? <small>{metadata}</small> : null}
</div>
{status ? <div className="nodedc-resource-row__status">{status}</div> : null}
{actions ? <div className="nodedc-resource-row__actions">{actions}</div> : null}
</div>;
}
export function ResourceList({ className, children, ...props }: HTMLAttributes<HTMLUListElement>) {
return <ul className={cn("nodedc-resource-list", className)} {...props}>{children}</ul>;
}
+1
View File
@@ -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";