Neutralize selected inbox accents
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
1871a602df
commit
50577b8c63
4 changed files with 258 additions and 71 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { act } from "react";
|
import { act } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import type { Issue } from "@paperclipai/shared";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { IssueRow } from "./IssueRow";
|
import { IssueRow } from "./IssueRow";
|
||||||
|
|
||||||
|
|
@ -14,6 +15,49 @@ vi.mock("@/lib/router", () => ({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
function createIssue(overrides: Partial<Issue> = {}): Issue {
|
||||||
|
return {
|
||||||
|
id: "issue-1",
|
||||||
|
identifier: "PAP-1",
|
||||||
|
companyId: "company-1",
|
||||||
|
projectId: null,
|
||||||
|
projectWorkspaceId: null,
|
||||||
|
goalId: null,
|
||||||
|
parentId: null,
|
||||||
|
title: "Inbox item",
|
||||||
|
description: null,
|
||||||
|
status: "todo",
|
||||||
|
priority: "medium",
|
||||||
|
assigneeAgentId: null,
|
||||||
|
assigneeUserId: null,
|
||||||
|
createdByAgentId: null,
|
||||||
|
createdByUserId: null,
|
||||||
|
issueNumber: 1,
|
||||||
|
requestDepth: 0,
|
||||||
|
billingCode: null,
|
||||||
|
assigneeAdapterOverrides: null,
|
||||||
|
executionWorkspaceId: null,
|
||||||
|
executionWorkspacePreference: null,
|
||||||
|
executionWorkspaceSettings: null,
|
||||||
|
checkoutRunId: null,
|
||||||
|
executionRunId: null,
|
||||||
|
executionAgentNameKey: null,
|
||||||
|
executionLockedAt: null,
|
||||||
|
startedAt: null,
|
||||||
|
completedAt: null,
|
||||||
|
cancelledAt: null,
|
||||||
|
hiddenAt: null,
|
||||||
|
createdAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||||
|
updatedAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||||
|
labels: [],
|
||||||
|
labelIds: [],
|
||||||
|
myLastTouchAt: null,
|
||||||
|
lastExternalCommentAt: null,
|
||||||
|
isUnreadForMe: false,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("IssueRow", () => {
|
describe("IssueRow", () => {
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
|
|
||||||
|
|
@ -28,45 +72,7 @@ describe("IssueRow", () => {
|
||||||
|
|
||||||
it("suppresses accent hover styling when the row is selected", () => {
|
it("suppresses accent hover styling when the row is selected", () => {
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
const issue = {
|
const issue = createIssue();
|
||||||
id: "issue-1",
|
|
||||||
identifier: "PAP-1",
|
|
||||||
companyId: "company-1",
|
|
||||||
projectId: null,
|
|
||||||
projectWorkspaceId: null,
|
|
||||||
goalId: null,
|
|
||||||
parentId: null,
|
|
||||||
title: "Inbox item",
|
|
||||||
description: null,
|
|
||||||
status: "todo",
|
|
||||||
priority: "medium",
|
|
||||||
assigneeAgentId: null,
|
|
||||||
assigneeUserId: null,
|
|
||||||
createdByAgentId: null,
|
|
||||||
createdByUserId: null,
|
|
||||||
issueNumber: 1,
|
|
||||||
requestDepth: 0,
|
|
||||||
billingCode: null,
|
|
||||||
assigneeAdapterOverrides: null,
|
|
||||||
executionWorkspaceId: null,
|
|
||||||
executionWorkspacePreference: null,
|
|
||||||
executionWorkspaceSettings: null,
|
|
||||||
checkoutRunId: null,
|
|
||||||
executionRunId: null,
|
|
||||||
executionAgentNameKey: null,
|
|
||||||
executionLockedAt: null,
|
|
||||||
startedAt: null,
|
|
||||||
completedAt: null,
|
|
||||||
cancelledAt: null,
|
|
||||||
hiddenAt: null,
|
|
||||||
createdAt: new Date("2026-03-11T00:00:00.000Z"),
|
|
||||||
updatedAt: new Date("2026-03-11T00:00:00.000Z"),
|
|
||||||
labels: [],
|
|
||||||
labelIds: [],
|
|
||||||
myLastTouchAt: null,
|
|
||||||
lastExternalCommentAt: null,
|
|
||||||
isUnreadForMe: false,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
root.render(<IssueRow issue={issue} selected />);
|
root.render(<IssueRow issue={issue} selected />);
|
||||||
|
|
@ -81,4 +87,30 @@ describe("IssueRow", () => {
|
||||||
root.unmount();
|
root.unmount();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("neutralizes selected status and unread dot accents", () => {
|
||||||
|
const root = createRoot(container);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(<IssueRow issue={createIssue()} selected unreadState="visible" />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const markReadButton = container.querySelector('button[aria-label="Mark as read"]');
|
||||||
|
const unreadDot = markReadButton?.querySelector("span");
|
||||||
|
const statusIcon = container.querySelector('span[class*="border-muted-foreground"]');
|
||||||
|
|
||||||
|
expect(markReadButton).not.toBeNull();
|
||||||
|
expect(markReadButton?.className).toContain("hover:bg-muted/80");
|
||||||
|
expect(markReadButton?.className).not.toContain("hover:bg-blue-500/20");
|
||||||
|
expect(unreadDot).not.toBeNull();
|
||||||
|
expect(unreadDot?.className).toContain("bg-muted-foreground/70");
|
||||||
|
expect(unreadDot?.className).not.toContain("bg-blue-600");
|
||||||
|
expect(statusIcon).not.toBeNull();
|
||||||
|
expect(statusIcon?.className).toContain("!border-muted-foreground");
|
||||||
|
expect(statusIcon?.className).toContain("!text-muted-foreground");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ export function IssueRow({
|
||||||
const identifier = issue.identifier ?? issue.id.slice(0, 8);
|
const identifier = issue.identifier ?? issue.id.slice(0, 8);
|
||||||
const showUnreadSlot = unreadState !== null;
|
const showUnreadSlot = unreadState !== null;
|
||||||
const showUnreadDot = unreadState === "visible" || unreadState === "fading";
|
const showUnreadDot = unreadState === "visible" || unreadState === "fading";
|
||||||
|
const selectedStatusClass = selected ? "!text-muted-foreground !border-muted-foreground" : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -58,7 +59,7 @@ export function IssueRow({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="shrink-0 pt-px sm:hidden">
|
<span className="shrink-0 pt-px sm:hidden">
|
||||||
{mobileLeading ?? <StatusIcon status={issue.status} />}
|
{mobileLeading ?? <StatusIcon status={issue.status} className={selectedStatusClass} />}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-0 flex-1 flex-col gap-1 sm:contents">
|
<span className="flex min-w-0 flex-1 flex-col gap-1 sm:contents">
|
||||||
<span className="line-clamp-2 text-sm sm:order-2 sm:min-w-0 sm:flex-1 sm:truncate sm:line-clamp-none">
|
<span className="line-clamp-2 text-sm sm:order-2 sm:min-w-0 sm:flex-1 sm:truncate sm:line-clamp-none">
|
||||||
|
|
@ -71,7 +72,7 @@ export function IssueRow({
|
||||||
{desktopMetaLeading ?? (
|
{desktopMetaLeading ?? (
|
||||||
<>
|
<>
|
||||||
<span className="hidden shrink-0 sm:inline-flex">
|
<span className="hidden shrink-0 sm:inline-flex">
|
||||||
<StatusIcon status={issue.status} />
|
<StatusIcon status={issue.status} className={selectedStatusClass} />
|
||||||
</span>
|
</span>
|
||||||
<span className="shrink-0 font-mono text-xs text-muted-foreground">
|
<span className="shrink-0 font-mono text-xs text-muted-foreground">
|
||||||
{identifier}
|
{identifier}
|
||||||
|
|
@ -113,12 +114,16 @@ export function IssueRow({
|
||||||
onMarkRead?.();
|
onMarkRead?.();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20"
|
className={cn(
|
||||||
|
"inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors",
|
||||||
|
selected ? "hover:bg-muted/80" : "hover:bg-blue-500/20",
|
||||||
|
)}
|
||||||
aria-label="Mark as read"
|
aria-label="Mark as read"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"block h-2 w-2 rounded-full bg-blue-600 transition-opacity duration-300 dark:bg-blue-400",
|
"block h-2 w-2 rounded-full transition-opacity duration-300",
|
||||||
|
selected ? "bg-muted-foreground/70" : "bg-blue-600 dark:bg-blue-400",
|
||||||
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
import { act } from "react";
|
import { act } from "react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import type { Issue } from "@paperclipai/shared";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { FailedRunInboxRow } from "./Inbox";
|
import { FailedRunInboxRow, InboxIssueMetaLeading } from "./Inbox";
|
||||||
|
|
||||||
vi.mock("@/lib/router", () => ({
|
vi.mock("@/lib/router", () => ({
|
||||||
Link: ({ children, className, ...props }: ComponentProps<"a">) => (
|
Link: ({ children, className, ...props }: ComponentProps<"a">) => (
|
||||||
|
|
@ -17,6 +18,49 @@ vi.mock("@/lib/router", () => ({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
function createIssue(overrides: Partial<Issue> = {}): Issue {
|
||||||
|
return {
|
||||||
|
id: "issue-1",
|
||||||
|
identifier: "PAP-904",
|
||||||
|
companyId: "company-1",
|
||||||
|
projectId: null,
|
||||||
|
projectWorkspaceId: null,
|
||||||
|
goalId: null,
|
||||||
|
parentId: null,
|
||||||
|
title: "Inbox item",
|
||||||
|
description: null,
|
||||||
|
status: "todo",
|
||||||
|
priority: "medium",
|
||||||
|
assigneeAgentId: null,
|
||||||
|
assigneeUserId: null,
|
||||||
|
createdByAgentId: null,
|
||||||
|
createdByUserId: null,
|
||||||
|
issueNumber: 904,
|
||||||
|
requestDepth: 0,
|
||||||
|
billingCode: null,
|
||||||
|
assigneeAdapterOverrides: null,
|
||||||
|
executionWorkspaceId: null,
|
||||||
|
executionWorkspacePreference: null,
|
||||||
|
executionWorkspaceSettings: null,
|
||||||
|
checkoutRunId: null,
|
||||||
|
executionRunId: null,
|
||||||
|
executionAgentNameKey: null,
|
||||||
|
executionLockedAt: null,
|
||||||
|
startedAt: null,
|
||||||
|
completedAt: null,
|
||||||
|
cancelledAt: null,
|
||||||
|
hiddenAt: null,
|
||||||
|
createdAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||||
|
updatedAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||||
|
labels: [],
|
||||||
|
labelIds: [],
|
||||||
|
myLastTouchAt: null,
|
||||||
|
lastExternalCommentAt: null,
|
||||||
|
isUnreadForMe: false,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("FailedRunInboxRow", () => {
|
describe("FailedRunInboxRow", () => {
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
|
|
||||||
|
|
@ -91,3 +135,47 @@ describe("FailedRunInboxRow", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("InboxIssueMetaLeading", () => {
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
container = document.createElement("div");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("neutralizes selected status and live accents", () => {
|
||||||
|
const root = createRoot(container);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(<InboxIssueMetaLeading issue={createIssue()} selected isLive />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusIcon = container.querySelector('span[class*="border-muted-foreground"]');
|
||||||
|
const liveBadge = container.querySelector('span[class*="px-1.5"][class*="bg-muted"]');
|
||||||
|
const liveBadgeLabel = Array.from(container.querySelectorAll("span")).find(
|
||||||
|
(node) => node.textContent === "Live" && node.className.includes("text-"),
|
||||||
|
);
|
||||||
|
const liveDot = container.querySelector('span[class*="bg-muted-foreground/70"]');
|
||||||
|
const pulseRing = container.querySelector('span[class*="animate-pulse"]');
|
||||||
|
|
||||||
|
expect(statusIcon).not.toBeNull();
|
||||||
|
expect(statusIcon?.className).toContain("!border-muted-foreground");
|
||||||
|
expect(statusIcon?.className).toContain("!text-muted-foreground");
|
||||||
|
expect(liveBadge).not.toBeNull();
|
||||||
|
expect(liveBadge?.className).toContain("bg-muted");
|
||||||
|
expect(liveBadgeLabel).not.toBeNull();
|
||||||
|
expect(liveBadgeLabel?.className).toContain("text-muted-foreground");
|
||||||
|
expect(liveBadgeLabel?.className).not.toContain("text-blue-600");
|
||||||
|
expect(liveDot).not.toBeNull();
|
||||||
|
expect(pulseRing).toBeNull();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,67 @@ function readIssueIdFromRun(run: HeartbeatRun): string | null {
|
||||||
|
|
||||||
|
|
||||||
type NonIssueUnreadState = "visible" | "fading" | "hidden" | null;
|
type NonIssueUnreadState = "visible" | "fading" | "hidden" | null;
|
||||||
|
const selectedInboxAccentClass = "!text-muted-foreground !border-muted-foreground";
|
||||||
|
|
||||||
|
function getSelectedUnreadButtonClass(selected: boolean): string {
|
||||||
|
return selected ? "hover:bg-muted/80" : "hover:bg-blue-500/20";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedUnreadDotClass(selected: boolean): string {
|
||||||
|
return selected ? "bg-muted-foreground/70" : "bg-blue-600 dark:bg-blue-400";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InboxIssueMetaLeading({
|
||||||
|
issue,
|
||||||
|
selected,
|
||||||
|
isLive,
|
||||||
|
}: {
|
||||||
|
issue: Issue;
|
||||||
|
selected: boolean;
|
||||||
|
isLive: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="hidden shrink-0 sm:inline-flex">
|
||||||
|
<StatusIcon
|
||||||
|
status={issue.status}
|
||||||
|
className={selected ? selectedInboxAccentClass : undefined}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="shrink-0 font-mono text-xs text-muted-foreground">
|
||||||
|
{issue.identifier ?? issue.id.slice(0, 8)}
|
||||||
|
</span>
|
||||||
|
{isLive && (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 sm:gap-1.5 sm:px-2",
|
||||||
|
selected ? "bg-muted" : "bg-blue-500/10",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
{!selected ? (
|
||||||
|
<span className="absolute inline-flex h-full w-full animate-pulse rounded-full bg-blue-400 opacity-75" />
|
||||||
|
) : null}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"relative inline-flex h-2 w-2 rounded-full",
|
||||||
|
selected ? "bg-muted-foreground/70" : "bg-blue-500",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"hidden text-[11px] font-medium sm:inline",
|
||||||
|
selected ? "text-muted-foreground" : "text-blue-600 dark:text-blue-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Live
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function FailedRunInboxRow({
|
export function FailedRunInboxRow({
|
||||||
run,
|
run,
|
||||||
|
|
@ -148,11 +209,15 @@ export function FailedRunInboxRow({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onMarkRead}
|
onClick={onMarkRead}
|
||||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20"
|
className={cn(
|
||||||
|
"inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors",
|
||||||
|
getSelectedUnreadButtonClass(selected),
|
||||||
|
)}
|
||||||
aria-label="Mark as read"
|
aria-label="Mark as read"
|
||||||
>
|
>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"block h-2 w-2 rounded-full bg-blue-600 transition-opacity duration-300 dark:bg-blue-400",
|
"block h-2 w-2 rounded-full transition-opacity duration-300",
|
||||||
|
getSelectedUnreadDotClass(selected),
|
||||||
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
||||||
)} />
|
)} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -300,11 +365,15 @@ function ApprovalInboxRow({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onMarkRead}
|
onClick={onMarkRead}
|
||||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20"
|
className={cn(
|
||||||
|
"inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors",
|
||||||
|
getSelectedUnreadButtonClass(selected),
|
||||||
|
)}
|
||||||
aria-label="Mark as read"
|
aria-label="Mark as read"
|
||||||
>
|
>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"block h-2 w-2 rounded-full bg-blue-600 transition-opacity duration-300 dark:bg-blue-400",
|
"block h-2 w-2 rounded-full transition-opacity duration-300",
|
||||||
|
getSelectedUnreadDotClass(selected),
|
||||||
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
||||||
)} />
|
)} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -402,6 +471,7 @@ function JoinRequestInboxRow({
|
||||||
onMarkRead,
|
onMarkRead,
|
||||||
onArchive,
|
onArchive,
|
||||||
archiveDisabled,
|
archiveDisabled,
|
||||||
|
selected = false,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
joinRequest: JoinRequest;
|
joinRequest: JoinRequest;
|
||||||
|
|
@ -412,6 +482,7 @@ function JoinRequestInboxRow({
|
||||||
onMarkRead?: () => void;
|
onMarkRead?: () => void;
|
||||||
onArchive?: () => void;
|
onArchive?: () => void;
|
||||||
archiveDisabled?: boolean;
|
archiveDisabled?: boolean;
|
||||||
|
selected?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const label =
|
const label =
|
||||||
|
|
@ -433,11 +504,15 @@ function JoinRequestInboxRow({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onMarkRead}
|
onClick={onMarkRead}
|
||||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20"
|
className={cn(
|
||||||
|
"inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors",
|
||||||
|
getSelectedUnreadButtonClass(selected),
|
||||||
|
)}
|
||||||
aria-label="Mark as read"
|
aria-label="Mark as read"
|
||||||
>
|
>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"block h-2 w-2 rounded-full bg-blue-600 transition-opacity duration-300 dark:bg-blue-400",
|
"block h-2 w-2 rounded-full transition-opacity duration-300",
|
||||||
|
getSelectedUnreadDotClass(selected),
|
||||||
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
unreadState === "fading" ? "opacity-0" : "opacity-100",
|
||||||
)} />
|
)} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1351,6 +1426,7 @@ export function Inbox() {
|
||||||
<JoinRequestInboxRow
|
<JoinRequestInboxRow
|
||||||
key={joinKey}
|
key={joinKey}
|
||||||
joinRequest={item.joinRequest}
|
joinRequest={item.joinRequest}
|
||||||
|
selected={isSelected}
|
||||||
onApprove={() => approveJoinMutation.mutate(item.joinRequest)}
|
onApprove={() => approveJoinMutation.mutate(item.joinRequest)}
|
||||||
onReject={() => rejectJoinMutation.mutate(item.joinRequest)}
|
onReject={() => rejectJoinMutation.mutate(item.joinRequest)}
|
||||||
isPending={approveJoinMutation.isPending || rejectJoinMutation.isPending}
|
isPending={approveJoinMutation.isPending || rejectJoinMutation.isPending}
|
||||||
|
|
@ -1393,27 +1469,13 @@ export function Inbox() {
|
||||||
? "pointer-events-none -translate-x-4 scale-[0.98] opacity-0 transition-all duration-200 ease-out"
|
? "pointer-events-none -translate-x-4 scale-[0.98] opacity-0 transition-all duration-200 ease-out"
|
||||||
: "transition-all duration-200 ease-out"
|
: "transition-all duration-200 ease-out"
|
||||||
}
|
}
|
||||||
desktopMetaLeading={(
|
desktopMetaLeading={
|
||||||
<>
|
<InboxIssueMetaLeading
|
||||||
<span className="hidden shrink-0 sm:inline-flex">
|
issue={issue}
|
||||||
<StatusIcon status={issue.status} />
|
selected={isSelected}
|
||||||
</span>
|
isLive={liveIssueIds.has(issue.id)}
|
||||||
<span className="shrink-0 font-mono text-xs text-muted-foreground">
|
/>
|
||||||
{issue.identifier ?? issue.id.slice(0, 8)}
|
}
|
||||||
</span>
|
|
||||||
{liveIssueIds.has(issue.id) && (
|
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-blue-500/10 px-1.5 py-0.5 sm:gap-1.5 sm:px-2">
|
|
||||||
<span className="relative flex h-2 w-2">
|
|
||||||
<span className="absolute inline-flex h-full w-full animate-pulse rounded-full bg-blue-400 opacity-75" />
|
|
||||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-blue-500" />
|
|
||||||
</span>
|
|
||||||
<span className="hidden text-[11px] font-medium text-blue-600 dark:text-blue-400 sm:inline">
|
|
||||||
Live
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
mobileMeta={
|
mobileMeta={
|
||||||
issue.lastExternalCommentAt
|
issue.lastExternalCommentAt
|
||||||
? `commented ${timeAgo(issue.lastExternalCommentAt)}`
|
? `commented ${timeAgo(issue.lastExternalCommentAt)}`
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue