Detailed changes
@@ -10,7 +10,7 @@ exports[`StatusTabs/Default matches snapshot 1`] = `
>
<svg
aria-hidden="true"
- class="lucide lucide-circle-dot size-4 group-[.active]:text-green-600 dark:group-[.active]:text-green-400"
+ class="lucide lucide-circle-dot size-4"
fill="none"
height="24"
stroke="currentColor"
@@ -34,7 +34,7 @@ exports[`StatusTabs/Default matches snapshot 1`] = `
</svg>
Open
<span
- class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none"
+ class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums"
>
12
</span>
@@ -44,7 +44,7 @@ exports[`StatusTabs/Default matches snapshot 1`] = `
>
<svg
aria-hidden="true"
- class="lucide lucide-circle-check size-4 group-[.active]:text-purple-600 dark:group-[.active]:text-purple-400"
+ class="lucide lucide-circle-check size-4"
fill="none"
height="24"
stroke="currentColor"
@@ -66,7 +66,7 @@ exports[`StatusTabs/Default matches snapshot 1`] = `
</svg>
Closed
<span
- class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none"
+ class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums"
>
5
</span>
@@ -46,21 +46,22 @@ export const Tab: LinkComponent<typeof TabComponent> = (props) => {
};
interface IndicatorProps {
+ active?: boolean;
className?: string;
}
-export function OpenIndicator({ className }: IndicatorProps) {
+export function OpenIndicator({ active, className }: IndicatorProps) {
return (
<CircleDot
- className={cn("size-4 group-[.active]:text-green-600 dark:group-[.active]:text-green-400", className)}
+ className={cn("size-4", active && "text-green-600 dark:text-green-400", className)}
/>
);
}
-export function ClosedIndicator({ className }: IndicatorProps) {
+export function ClosedIndicator({ active, className }: IndicatorProps) {
return (
<CircleCheck
- className={cn("size-4 group-[.active]:text-purple-600 dark:group-[.active]:text-purple-400", className)}
+ className={cn("size-4", active && "text-purple-600 dark:text-purple-400", className)}
/>
);
}
@@ -71,7 +72,7 @@ interface CountProps {
export function Count({ children }: CountProps) {
return (
- <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none">
+ <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums">
{children}
</span>
);
@@ -1,13 +1,14 @@
import { useReadQuery } from "@apollo/client/react";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { formatDistanceToNow } from "date-fns";
-import { CircleDot, CircleCheck, Search } from "lucide-react";
+import { Search } from "lucide-react";
import { useMemo, useState } from "react";
import * as v from "valibot";
import { type BugListQuery, BugListDocument } from "@/__generated__/graphql";
import { IssueFilters } from "@/components/shared/issue-filters";
import * as IssueRow from "@/components/shared/issue-row";
+import * as StatusTabs from "@/components/shared/status-tabs";
import { LabelBadgeLink } from "@/components/shared/label-badge";
import { EmptyState } from "@/components/shared/empty-state";
import * as Pagination from "@/components/shared/pagination";
@@ -17,7 +18,6 @@ import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import type { SortValue, StatusFilter } from "@/lib/query-utils";
import { buildBaseQuery, buildQueryString, parseQueryString } from "@/lib/query-utils";
-import { cn } from "@/lib/utils";
const issuesSearchSchema = v.object({
q: v.fallback(v.string(), "status:open"),
@@ -182,53 +182,28 @@ function RouteComponent() {
<div className="border-border rounded-md border">
{/* Open / Closed toggle + filter dropdowns */}
<div className="border-border flex items-center gap-2 overflow-x-auto border-b px-4 py-2">
- <div className="flex shrink-0 items-center gap-1">
- <Link
+ <StatusTabs.Root className="shrink-0">
+ <StatusTabs.Tab
to="/$repo/issues"
- params={{ repo: repo }}
+ params={{ repo }}
search={{ q: queryWithStatus("open"), after: "" }}
- className={cn(
- "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
- statusFilter === "open"
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
- )}
+ className={statusFilter === "open" ? "bg-accent text-accent-foreground" : ""}
>
- <CircleDot
- className={cn(
- "size-4",
- statusFilter === "open" && "text-green-600 dark:text-green-400",
- )}
- />
+ <StatusTabs.OpenIndicator active={statusFilter === "open"} />
Open
- <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums">
- {openCount}
- </span>
- </Link>
-
- <Link
+ <StatusTabs.Count>{openCount}</StatusTabs.Count>
+ </StatusTabs.Tab>
+ <StatusTabs.Tab
to="/$repo/issues"
- params={{ repo: repo }}
+ params={{ repo }}
search={{ q: queryWithStatus("closed"), after: "" }}
- className={cn(
- "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
- statusFilter === "closed"
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
- )}
+ className={statusFilter === "closed" ? "bg-accent text-accent-foreground" : ""}
>
- <CircleCheck
- className={cn(
- "size-4",
- statusFilter === "closed" && "text-purple-600 dark:text-purple-400",
- )}
- />
+ <StatusTabs.ClosedIndicator active={statusFilter === "closed"} />
Closed
- <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums">
- {closedCount}
- </span>
- </Link>
- </div>
+ <StatusTabs.Count>{closedCount}</StatusTabs.Count>
+ </StatusTabs.Tab>
+ </StatusTabs.Root>
<div className="ml-auto">
<IssueFilters
@@ -6,22 +6,18 @@
import { useReadQuery } from "@apollo/client/react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { formatDistanceToNow } from "date-fns";
-import {
- CircleDot,
- CircleCheck,
- ShieldCheck,
-} from "lucide-react";
+import { CircleDot, CircleCheck, ShieldCheck } from "lucide-react";
import * as v from "valibot";
import { type UserProfileQuery, UserProfileDocument } from "@/__generated__/graphql";
import * as IssueRow from "@/components/shared/issue-row";
import { LabelBadge } from "@/components/shared/label-badge";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { BackLink } from "@/components/ui/back-link";
import { EmptyState } from "@/components/shared/empty-state";
import * as Pagination from "@/components/shared/pagination";
+import * as StatusTabs from "@/components/shared/status-tabs";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { BackLink } from "@/components/ui/back-link";
import { Skeleton } from "@/components/ui/skeleton";
-import { cn } from "@/lib/utils";
const profileSearchSchema = v.object({
status: v.fallback(v.picklist(["open", "closed"]), "open"),
@@ -115,53 +111,28 @@ function RouteComponent() {
{/* ── Issue list ─────────────────────────────────────────────────── */}
<div className="border-border rounded-md border">
{/* Open / Closed toggle */}
- <div className="border-border flex items-center gap-1 border-b px-4 py-2">
- <Link
+ <StatusTabs.Root className="border-border border-b px-4 py-2">
+ <StatusTabs.Tab
to="/$repo/user/$id"
params={{ repo, id }}
search={{ status: "open", after: "" }}
- className={cn(
- "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
- statusFilter === "open"
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
- )}
+ className={statusFilter === "open" ? "bg-accent text-accent-foreground" : ""}
>
- <CircleDot
- className={cn(
- "size-4",
- statusFilter === "open" && "text-green-600 dark:text-green-400",
- )}
- />
+ <StatusTabs.OpenIndicator active={statusFilter === "open"} />
Open
- <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none">
- {openCount}
- </span>
- </Link>
-
- <Link
+ <StatusTabs.Count>{openCount}</StatusTabs.Count>
+ </StatusTabs.Tab>
+ <StatusTabs.Tab
to="/$repo/user/$id"
params={{ repo, id }}
search={{ status: "closed", after: "" }}
- className={cn(
- "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
- statusFilter === "closed"
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
- )}
+ className={statusFilter === "closed" ? "bg-accent text-accent-foreground" : ""}
>
- <CircleCheck
- className={cn(
- "size-4",
- statusFilter === "closed" && "text-purple-600 dark:text-purple-400",
- )}
- />
+ <StatusTabs.ClosedIndicator active={statusFilter === "closed"} />
Closed
- <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none">
- {closedCount}
- </span>
- </Link>
- </div>
+ <StatusTabs.Count>{closedCount}</StatusTabs.Count>
+ </StatusTabs.Tab>
+ </StatusTabs.Root>
{bugs?.nodes.length === 0 && (
<EmptyState>No {statusFilter} issues.</EmptyState>