Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/app/components/navigation/role-nav.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { formatBadgeCount, getBadgeAriaLabel } from "./role-nav";

describe("role-nav badge scheme", () => {
describe("formatBadgeCount", () => {
it("returns string representation of counts under 100", () => {
expect(formatBadgeCount(0)).toBe("0");
expect(formatBadgeCount(5)).toBe("5");
expect(formatBadgeCount(99)).toBe("99");
});

it("caps counts greater than 99 at '99+'", () => {
expect(formatBadgeCount(100)).toBe("99+");
expect(formatBadgeCount(1000)).toBe("99+");
expect(formatBadgeCount(9999)).toBe("99+");
});
});

describe("getBadgeAriaLabel", () => {
it("announces dot badge correctly", () => {
expect(getBadgeAriaLabel({ type: "dot" })).toBe("New updates available");
});

it("announces singular count correctly", () => {
expect(getBadgeAriaLabel({ type: "count", value: 1 })).toBe("1 new update");
});

it("announces plural counts correctly", () => {
expect(getBadgeAriaLabel({ type: "count", value: 5 })).toBe("5 new updates");
expect(getBadgeAriaLabel({ type: "count", value: 100 })).toBe("100 new updates");
expect(getBadgeAriaLabel({ type: "count", value: 9999 })).toBe("9999 new updates");
});
});
});
23 changes: 23 additions & 0 deletions src/app/components/navigation/role-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const ALL_ROLES: UserRole[] = ["supplier", "buyer", "admin"];

// ─── Nav item shape ───────────────────────────────────────────────────────────

export type NavBadge =
| { type: "dot" }
| { type: "count"; value: number };

export interface NavItem {
/** Route href */
href: string;
Expand All @@ -35,6 +39,25 @@ export interface NavItem {
icon: string;
/** aria-label override for screen readers when label alone is ambiguous */
ariaLabel?: string;
/** Optional badge indicator (dot or count) */
badge?: NavBadge;
}

/**
* Formats a badge count for visual display, capping at "99+".
*/
export function formatBadgeCount(value: number): string {
return value > 99 ? "99+" : value.toString();
}

/**
* Generates an accessible screen reader announcement for the badge.
*/
export function getBadgeAriaLabel(badge: NavBadge): string {
if (badge.type === "dot") {
return "New updates available";
}
return `${badge.value} new ${badge.value === 1 ? "update" : "updates"}`;
}

// ─── Shared primitives (common to every role) ─────────────────────────────────
Expand Down
Loading