Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions command-center-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

- **`EntitySummary` takes a `titleLead`.** A detail page can show the entity's icon or avatar
before the summary title, at the size the host chooses. The lead and the title sit in a new
`cc-entity-summary__heading` row, and a lead that renders nothing takes no space. Without
`titleLead` the title renders exactly as before.

## 0.5.17

Compatibility axes: the packaged skill `general/maintain-command-center-code-repository`. No npm
Expand Down
1 change: 1 addition & 0 deletions command-center-sdk/docs/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export function ServiceDetail({ service }: { service: Service }) {
headerActions={<Button onClick={() => openEditDialog(service)}>Edit</Button>}
summary={
<EntitySummary
titleLead={<ServiceIcon service={service} />}
summary={{
entity: { id: service.uid, type: "service", title: service.name },
badges: [{ key: "status", label: service.status }],
Expand Down
28 changes: 28 additions & 0 deletions command-center-sdk/src/views/EntitySummary.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,32 @@ describe("EntitySummary field information", () => {
await act(async () => toggle.click());
expect(container.querySelector(".cc-entity-summary__info-note")).toBeNull();
});

it("puts the title lead before the title and leaves the title alone without one", async () => {
const container = document.createElement("div");
document.body.append(container);
const root = createRoot(container);
roots.push(root);
const summary = {
entity: { id: "agent-1", type: "agent", title: "Tutorial Helper" },
badges: [],
inline_fields: [],
highlight_fields: [],
stats: [],
};

await act(async () => {
root.render(<EntitySummary summary={summary} titleLead={<img alt="" data-testid="lead" />} />);
});
const heading = container.querySelector(".cc-entity-summary__heading")!;
expect(heading.firstElementChild?.className).toBe("cc-entity-summary__title-lead");
expect(heading.querySelector("[data-testid='lead']")).not.toBeNull();
expect(heading.querySelector("h2")?.textContent).toBe("Tutorial Helper");

await act(async () => {
root.render(<EntitySummary summary={summary} />);
});
expect(container.querySelector(".cc-entity-summary__heading")).toBeNull();
expect(container.querySelector(".cc-entity-summary__identity > h2")?.textContent).toBe("Tutorial Helper");
});
});
15 changes: 14 additions & 1 deletion command-center-sdk/src/views/EntitySummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface EntitySummaryProps {
onLinkSelect?: (url: string, item: EntitySummaryItem) => void;
onRemoveLabel?: (label: string) => Promise<void> | void;
onLabelMutationError?: (error: unknown) => void;
/**
* Shown before the title, usually the entity's icon or avatar. The host sizes it; a lead that
* renders nothing takes no space.
*/
titleLead?: ReactNode;
}

function getValue(value: unknown): string {
Expand Down Expand Up @@ -128,6 +133,7 @@ export function EntitySummary({
onRemoveLabel,
renderFieldLead,
summary,
titleLead,
}: EntitySummaryProps) {
const summaryLabels = summary.label_management?.labels ?? summary.labels ?? [];
const [labels, setLabels] = useState(summaryLabels);
Expand Down Expand Up @@ -194,7 +200,14 @@ export function EntitySummary({
<section className="cc-entity-summary">
<div className="cc-entity-summary__top">
<div className="cc-entity-summary__identity">
<h2>{summary.entity.title}</h2>
{titleLead ? (
<div className="cc-entity-summary__heading">
<span className="cc-entity-summary__title-lead">{titleLead}</span>
<h2>{summary.entity.title}</h2>
</div>
) : (
<h2>{summary.entity.title}</h2>
)}
<div className="cc-entity-summary__inline-fields">
{summary.inline_fields.map((field) => {
const value = getValue(field.value);
Expand Down
3 changes: 2 additions & 1 deletion command-center-sdk/src/views/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,5 @@ fitting rules are pure functions in `detail-tab-layout.ts`, and its More menu is
in action mode. Hosts resolve tabs once with `resolveResourceDetailTabs` from `/resource`, which is
the only place `isVisible` and function-valued `disabled` run, and switch the body on that result. `EntitySummary` and `CollapsedEntitySummary` render normalized backend-neutral summary
data; hosts contribute icons, link handling, editing, label mutations, and notifications through
callbacks. No view in this module knows a consumer endpoint or application-owned search parameter.
callbacks. `EntitySummary`'s `titleLead` puts a host node, such as the entity's icon, before the
title; the host sizes it, and a lead that renders nothing takes no space. No view in this module knows a consumer endpoint or application-owned search parameter.
16 changes: 16 additions & 0 deletions command-center-sdk/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,22 @@ h3),
min-width: 0;
}

.cc-entity-summary__heading {
align-items: center;
display: flex;
gap: 0.6rem;
min-width: 0;
}

.cc-entity-summary__title-lead {
display: inline-flex;
flex-shrink: 0;
}

.cc-entity-summary__title-lead:empty {
display: none;
}

.cc-entity-summary h2 {
font-size: var(--font-size-markdown-h1);
font-weight: var(--font-weight-semibold);
Expand Down
Loading