mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Refine external object property pills
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
bb8ad4f0fa
commit
d4c4861abd
5 files changed
+47
-14
No files matched your search
@@ -91,7 +91,7 @@ describe("ExternalObjectPill", () => {
|
||||
expect(html).not.toContain("×");
|
||||
});
|
||||
|
||||
it("uses provider display keys, icons, visible status, and a truncated title when supplied", () => {
|
||||
it("uses the object link label, provider icon, and visible status when supplied", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ExternalObjectPill
|
||||
object={{
|
||||
@@ -108,9 +108,10 @@ describe("ExternalObjectPill", () => {
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain("Github Pull Request");
|
||||
expect(html).toContain("Merged");
|
||||
expect(html).toContain("acme/web#241: Add rich object presentation metadata");
|
||||
expect(html).toContain("acme/web#241");
|
||||
expect(html).toContain("text-violet-600");
|
||||
expect(html).not.toContain("Github Pull Request");
|
||||
expect(html).toContain('aria-label="GitHub pull request — Merged: acme/web#241: Add rich object presentation metadata"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,32 @@ export interface ExternalObjectPillData {
|
||||
url?: string | null;
|
||||
}
|
||||
|
||||
function githubObjectLabel(url: string | null | undefined): string | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.hostname !== "github.com") return null;
|
||||
const [, owner, repo, kind, number] = parsed.pathname.split("/");
|
||||
if (!owner || !repo || !number) return null;
|
||||
if (kind !== "pull" && kind !== "issues") return null;
|
||||
return `${owner}/${repo}#${number}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function externalObjectValueLabel(object: ExternalObjectPillData, fallback: string): string {
|
||||
const githubLabel = object.providerKey === "github" ? githubObjectLabel(object.url) : null;
|
||||
return githubLabel ?? object.displayTitle?.trim() ?? fallback;
|
||||
}
|
||||
|
||||
function externalObjectPillTone(object: ExternalObjectPillData): string {
|
||||
if (object.statusIconKey === "git-merge") {
|
||||
return "text-violet-600 border-violet-600 dark:text-violet-400 dark:border-violet-400";
|
||||
}
|
||||
return externalObjectStatusIcon[object.statusCategory] ?? externalObjectStatusIconDefault;
|
||||
}
|
||||
|
||||
interface ExternalObjectPillProps {
|
||||
object: ExternalObjectPillData;
|
||||
/** Optional external mention count (renders as `×N` superscript when > 1). */
|
||||
@@ -59,11 +85,12 @@ export function ExternalObjectPill({
|
||||
children,
|
||||
inert,
|
||||
}: ExternalObjectPillProps) {
|
||||
const tone = externalObjectStatusIcon[object.statusCategory] ?? externalObjectStatusIconDefault;
|
||||
const tone = externalObjectPillTone(object);
|
||||
const overlay = externalObjectLivenessOverlay[object.liveness] ?? "";
|
||||
const providerLabel = externalObjectProviderLabel(object.providerKey);
|
||||
const typeLabel = externalObjectTypeLabel(object.objectType);
|
||||
const displayKey = object.displayKey?.trim() || `${providerLabel} ${typeLabel}`;
|
||||
const valueLabel = externalObjectValueLabel(object, displayKey);
|
||||
const statusLabel = object.statusLabel ?? externalObjectCategoryLabel(object.statusCategory);
|
||||
const livenessLabel = externalObjectLivenessLabel(object.liveness);
|
||||
const ProviderIcon = externalObjectIconForKey(object.iconKey);
|
||||
@@ -88,8 +115,7 @@ export function ExternalObjectPill({
|
||||
: object.displayTitle ?? displayKey;
|
||||
const labelText = children ?? (
|
||||
<>
|
||||
<span className="font-medium">{displayKey}</span>
|
||||
<span className="inline-flex items-center gap-0.5 rounded-full bg-background/70 px-1 text-[10px] font-medium">
|
||||
<span className="inline-flex items-center gap-0.5 rounded-full border border-current bg-background/70 px-1 text-[10px] font-medium">
|
||||
<ExternalObjectStatusIcon
|
||||
category={object.statusCategory}
|
||||
liveness={object.liveness}
|
||||
@@ -99,9 +125,7 @@ export function ExternalObjectPill({
|
||||
/>
|
||||
<span>{statusLabel}</span>
|
||||
</span>
|
||||
{object.displayTitle ? (
|
||||
<span className="max-w-[16rem] truncate text-muted-foreground/80">{object.displayTitle}</span>
|
||||
) : null}
|
||||
<span className="max-w-[16rem] truncate font-medium">{valueLabel}</span>
|
||||
</>
|
||||
);
|
||||
const countSuffix = typeof sourceCount === "number" && sourceCount > 1 ? (
|
||||
|
||||
@@ -53,7 +53,9 @@ export function ExternalObjectStatusIcon({
|
||||
}: ExternalObjectStatusIconProps) {
|
||||
const reducedMotion = usePrefersReducedMotion();
|
||||
const Icon = externalObjectIconForKey(statusIconKey) ?? externalObjectIconForCategory(category);
|
||||
const tone = externalObjectStatusIcon[category] ?? externalObjectStatusIconDefault;
|
||||
const tone = statusIconKey === "git-merge"
|
||||
? "text-violet-600 border-violet-600 dark:text-violet-400 dark:border-violet-400"
|
||||
: externalObjectStatusIcon[category] ?? externalObjectStatusIconDefault;
|
||||
const livenessSuffix = liveness === "fresh" || liveness === "unknown"
|
||||
? ""
|
||||
: ` (${externalObjectLivenessLabel(liveness)})`;
|
||||
|
||||
@@ -1855,12 +1855,18 @@ describe("IssueProperties", () => {
|
||||
|
||||
expect(container.textContent).toContain("Github Pull Request");
|
||||
expect(container.textContent).toContain("Github Issue");
|
||||
expect(container.textContent).toContain("acme/web#241");
|
||||
expect(container.textContent).toContain("Merged");
|
||||
expect(container.textContent).toContain("Open");
|
||||
expect(container.textContent).not.toContain("External objects");
|
||||
const label = Array.from(container.querySelectorAll("span"))
|
||||
.find((span) => span.textContent === "Github Pull Request");
|
||||
expect(label?.querySelector("svg")).toBeTruthy();
|
||||
const pullRequestLink = Array.from(container.querySelectorAll("a"))
|
||||
.find((anchor) => anchor.getAttribute("href") === "https://github.com/acme/web/pull/241");
|
||||
expect(pullRequestLink?.textContent).toContain("acme/web#241");
|
||||
expect(pullRequestLink?.textContent).toContain("Merged");
|
||||
expect(pullRequestLink?.textContent).not.toContain("Github Pull Request");
|
||||
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
@@ -211,9 +211,9 @@ function externalObjectRowLabel(group: IssueExternalObjectGroup): React.ReactNod
|
||||
const displayKey = externalObjectRowDisplayKey(group);
|
||||
const Icon = externalObjectIconForKey(pill.iconKey);
|
||||
return (
|
||||
<span className="inline-flex min-w-0 items-center gap-1">
|
||||
{Icon ? <Icon aria-hidden="true" className="h-3 w-3 shrink-0" /> : null}
|
||||
<span className="truncate">{displayKey}</span>
|
||||
<span className="inline-flex min-w-0 items-start gap-1">
|
||||
{Icon ? <Icon aria-hidden="true" className="h-3 w-3 shrink-0 mt-0.5" /> : null}
|
||||
<span className="whitespace-normal break-words leading-tight">{displayKey}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -2254,7 +2254,7 @@ export function IssueProperties({
|
||||
<PropertyRow
|
||||
key={group.object?.id ?? `${pill.providerKey}:${pill.objectType}:${pill.url ?? "anon"}`}
|
||||
label={externalObjectRowLabel(externalObject)}
|
||||
labelClassName="w-32"
|
||||
labelClassName="w-24 max-w-24 whitespace-normal leading-tight"
|
||||
>
|
||||
<ExternalObjectPill
|
||||
object={pill}
|
||||
|
||||
Reference in new issue
Block a user