mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): add unread dot to workspace rail for channel unreads (#1637)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
parent
9683ef8079
commit
f968601ef6
@@ -25,22 +25,25 @@ describe("workspaceRailIndicators", () => {
|
||||
it("shows no badge for an observed workspace with unread but no mentions", () => {
|
||||
const r = workspaceRailIndicators({ hasUnread: true, state: "ready" });
|
||||
assert.equal(r.showBadge, false);
|
||||
assert.equal(r.showDot, true);
|
||||
assert.equal(r.pending, false);
|
||||
});
|
||||
|
||||
it("shows no badge for an observed workspace with no unread", () => {
|
||||
it("shows no badge and no dot for an observed workspace with no unread", () => {
|
||||
const r = workspaceRailIndicators({ hasUnread: false, state: "ready" });
|
||||
assert.equal(r.showBadge, false);
|
||||
assert.equal(r.showDot, false);
|
||||
assert.equal(r.pending, false);
|
||||
});
|
||||
|
||||
it("shows a mention badge with the count when mentions are present", () => {
|
||||
it("shows a mention badge with the count when mentions are present — no dot", () => {
|
||||
const r = workspaceRailIndicators({
|
||||
hasUnread: true,
|
||||
count: 3,
|
||||
state: "ready",
|
||||
});
|
||||
assert.equal(r.showBadge, true);
|
||||
assert.equal(r.showDot, false);
|
||||
assert.equal(r.mentionCount, 3);
|
||||
assert.equal(r.badgeLabel, "3");
|
||||
});
|
||||
@@ -54,30 +57,33 @@ describe("workspaceRailIndicators", () => {
|
||||
assert.equal(r.badgeLabel, "99+");
|
||||
});
|
||||
|
||||
it("never reports mentions for an unobserved (unknown) workspace", () => {
|
||||
it("never reports mentions or dot for an unobserved (unknown) workspace", () => {
|
||||
const r = workspaceRailIndicators({
|
||||
hasUnread: true,
|
||||
count: 5,
|
||||
state: "unknown",
|
||||
});
|
||||
assert.equal(r.showBadge, false);
|
||||
assert.equal(r.showDot, false);
|
||||
assert.equal(r.mentionCount, 0);
|
||||
assert.equal(r.pending, true);
|
||||
});
|
||||
|
||||
it("treats loading as pending, not as no-unread", () => {
|
||||
it("treats loading as pending — no badge, no dot", () => {
|
||||
const r = workspaceRailIndicators({ hasUnread: false, state: "loading" });
|
||||
assert.equal(r.pending, true);
|
||||
assert.equal(r.showBadge, false);
|
||||
assert.equal(r.showDot, false);
|
||||
});
|
||||
|
||||
it("never reports mentions on an errored observation", () => {
|
||||
it("never reports mentions or dot on an errored observation", () => {
|
||||
const r = workspaceRailIndicators({
|
||||
hasUnread: true,
|
||||
count: 2,
|
||||
state: "error",
|
||||
});
|
||||
assert.equal(r.showBadge, false);
|
||||
assert.equal(r.showDot, false);
|
||||
assert.equal(r.mentionCount, 0);
|
||||
assert.equal(r.pending, false);
|
||||
});
|
||||
|
||||
@@ -45,21 +45,29 @@ export function workspaceInitials(name: string): string {
|
||||
/**
|
||||
* Presentation decisions for one workspace button, derived from its observed
|
||||
* mention state. Pure so it can be unit-tested without a DOM. The `state` guard
|
||||
* ensures we NEVER render a mention badge for a relay we could not observe
|
||||
* ensures we NEVER render any indicator for a relay we could not observe
|
||||
* (`unknown`/`loading`/`error`) — only a `ready` observation is trusted.
|
||||
*
|
||||
* Two-tier indicator system:
|
||||
* - `showBadge`: numeric mention count (mentions/thread-replies present).
|
||||
* - `showDot`: plain unread dot when there are regular channel unreads but no
|
||||
* mentions. Mutually exclusive with `showBadge` by construction.
|
||||
*/
|
||||
export function workspaceRailIndicators(unread: WorkspaceUnreadState): {
|
||||
mentionCount: number;
|
||||
showBadge: boolean;
|
||||
showDot: boolean;
|
||||
pending: boolean;
|
||||
badgeLabel: string;
|
||||
} {
|
||||
const observed = unread.state === "ready";
|
||||
const mentionCount = observed ? (unread.count ?? 0) : 0;
|
||||
const showBadge = mentionCount > 0;
|
||||
const showDot = observed && unread.hasUnread && !showBadge;
|
||||
return {
|
||||
mentionCount,
|
||||
showBadge,
|
||||
showDot,
|
||||
pending: unread.state === "unknown" || unread.state === "loading",
|
||||
badgeLabel:
|
||||
mentionCount > MAX_BADGE ? `${MAX_BADGE}+` : String(mentionCount),
|
||||
@@ -81,12 +89,14 @@ function WorkspaceButton({
|
||||
onSwitch: () => void;
|
||||
menu: React.ReactNode;
|
||||
}) {
|
||||
const { mentionCount, showBadge, pending, badgeLabel } =
|
||||
const { mentionCount, showBadge, showDot, pending, badgeLabel } =
|
||||
workspaceRailIndicators(unread);
|
||||
|
||||
const tooltipLabel = showBadge
|
||||
? `${workspace.name} — ${mentionCount} mention${mentionCount === 1 ? "" : "s"}`
|
||||
: workspace.name;
|
||||
: showDot
|
||||
? `${workspace.name} — unread`
|
||||
: workspace.name;
|
||||
|
||||
return (
|
||||
<ContextMenu>
|
||||
@@ -129,6 +139,13 @@ function WorkspaceButton({
|
||||
>
|
||||
{badgeLabel}
|
||||
</span>
|
||||
) : showDot ? (
|
||||
<span
|
||||
className="absolute -bottom-0.5 -right-0.5 h-2 w-2 shrink-0 rounded-full bg-primary ring-2 ring-sidebar"
|
||||
data-testid={`workspace-rail-unread-dot-${workspace.id}`}
|
||||
>
|
||||
<span className="sr-only">unread</span>
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
|
||||
Reference in New Issue
Block a user