feat(desktop): unify unread pills into one shared UnreadPill component (#1111)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-06-18 07:56:33 -07:00
committed by GitHub
co-authored by Brain
parent 762ca43d26
commit 826945e1bb
4 changed files with 59 additions and 46 deletions
@@ -1,5 +1,5 @@
import * as React from "react";
import { ArrowDown, ArrowUp, Hash } from "lucide-react";
import { Hash } from "lucide-react";
import {
selectTimelineBodySurface,
@@ -11,9 +11,9 @@ import type { UserProfileLookup } from "@/features/profile/lib/identity";
import type { ChannelType } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
import { channelChrome } from "@/shared/layout/chromeLayout";
import { Button } from "@/shared/ui/button";
import { Spinner } from "@/shared/ui/spinner";
import { TooltipProvider } from "@/shared/ui/tooltip";
import { UnreadPill, unreadCountLabel } from "@/shared/ui/UnreadPill";
import { UserAvatar } from "@/shared/ui/UserAvatar";
import { TimelineSkeleton, useTimelineSkeletonRows } from "./TimelineSkeleton";
import { TimelineMessageList } from "./TimelineMessageList";
@@ -313,17 +313,12 @@ const MessageTimelineBase = React.forwardRef<
channelChrome.top,
)}
>
<Button
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-primary/40 bg-primary/10 px-2.5 text-2xs font-medium text-primary shadow-xs backdrop-blur-sm hover:bg-primary/20 [&_svg]:size-4"
data-testid="message-unread-pill"
<UnreadPill
direction="up"
label={unreadCountLabel(unreadCount)}
onClick={handleJumpToOldestUnread}
size="sm"
type="button"
variant="outline"
>
<ArrowUp aria-hidden />
{`${unreadCount} new message${unreadCount === 1 ? "" : "s"}`}
</Button>
testId="message-unread-pill"
/>
</div>
) : null}
<div
@@ -537,21 +532,18 @@ const MessageTimelineBase = React.forwardRef<
hasComposerOverlay ? "bottom-36" : "bottom-4",
)}
>
<Button
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-border/50 bg-background/85 px-2.5 text-2xs font-medium text-muted-foreground shadow-xs backdrop-blur-sm hover:bg-muted/70 hover:text-foreground [&_svg]:size-4"
data-testid="message-scroll-to-latest"
<UnreadPill
direction="down"
label={
newMessageCount > 0
? unreadCountLabel(newMessageCount)
: "Jump to latest"
}
onClick={() => {
scrollToBottom("smooth");
}}
size="sm"
type="button"
variant="outline"
>
<ArrowDown aria-hidden />
{newMessageCount > 0
? `${newMessageCount} new message${newMessageCount === 1 ? "" : "s"}`
: "Jump to latest"}
</Button>
testId="message-scroll-to-latest"
/>
</div>
) : null}
</div>
@@ -1,8 +1,6 @@
// biome-ignore format: keep compact to stay within file size limit
import {
Activity,
ArrowDown,
ArrowUp,
Bot,
FolderGit2,
Home,
@@ -556,7 +554,6 @@ export function AppSidebar({
{unreadAboveCount > 0 ? (
<MoreUnreadButton
count={unreadAboveCount}
icon={<ArrowUp />}
onClick={scrollToNextAbove}
position="top"
testId="sidebar-more-unread-above"
@@ -768,7 +765,6 @@ export function AppSidebar({
<MoreUnreadButton
bottomClassName={unreadBelowBottomClass}
count={unreadBelowCount}
icon={<ArrowDown />}
onClick={scrollToNextBelow}
position="bottom"
testId="sidebar-more-unread-below"
@@ -1,21 +1,14 @@
import type * as React from "react";
import { Button } from "@/shared/ui/button";
const MORE_UNREAD_BUTTON_CLASS =
"h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-2xs font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-4";
import { UnreadPill, unreadCountLabel } from "@/shared/ui/UnreadPill";
export function MoreUnreadButton({
bottomClassName = "bottom-0",
count,
icon,
onClick,
position,
testId,
}: {
bottomClassName?: string;
count: number;
icon: React.ReactNode;
onClick: () => void;
position: "top" | "bottom";
testId: string;
@@ -24,17 +17,12 @@ export function MoreUnreadButton({
<div
className={`pointer-events-none absolute inset-x-0 z-10 flex justify-center py-1 ${position === "top" ? "top-0" : bottomClassName}`}
>
<Button
className={`pointer-events-auto ${MORE_UNREAD_BUTTON_CLASS}`}
data-testid={testId}
<UnreadPill
direction={position === "top" ? "up" : "down"}
label={unreadCountLabel(count)}
onClick={onClick}
size="sm"
type="button"
variant="ghost"
>
{icon}
{count} more unread
</Button>
testId={testId}
/>
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
import { ArrowDown, ArrowUp } from "lucide-react";
import { Button } from "@/shared/ui/button";
const UNREAD_PILL_CLASS =
"pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-primary/40 bg-primary/10 px-2.5 text-2xs font-medium text-primary shadow-xs backdrop-blur-sm hover:bg-primary/20 [&_svg]:size-4";
export function unreadCountLabel(count: number) {
return `${count} new message${count === 1 ? "" : "s"}`;
}
export function UnreadPill({
direction,
label,
onClick,
testId,
}: {
direction: "up" | "down";
label: string;
onClick: () => void;
testId: string;
}) {
const Arrow = direction === "up" ? ArrowUp : ArrowDown;
return (
<Button
className={UNREAD_PILL_CLASS}
data-testid={testId}
onClick={onClick}
size="sm"
type="button"
variant="outline"
>
<Arrow aria-hidden />
{label}
</Button>
);
}