fix(site): use singular pagination labels for one result (#30519)

> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Fix pagination summaries such as “Showing 1 to 1 of 1 groups” and
“Showing 1 to 1 of 1 workspaces”. All nine callers now supply explicit
singular/plural labels, selected from the total result count rather than
the number of rows on the current page.

Preserves empty and filtered copy, loading states, capped counts, and
number formatting. Extends existing Storybook coverage for single
results and one-row final pages.

<details>
<summary>Implementation decisions and verification</summary>

- Follow the existing explicit-label and `=== 1` convention without
stripping suffixes or adding a dependency.
- Require both labels through the shared API; keep capped totals plural
because they are not exact counts.
- Passed 136 targeted Vitest tests, 109 Storybook tests, `pnpm format`,
`pnpm check`, full `pnpm lint`, and FE1–FE10 review.
- Browser-verified rendered wording. Screenshots were returned in chat;
direct image inspection was unavailable.

</details>
This commit is contained in:
Jake Howell
2026-10-14 14:26:12 +11:00
committed by GitHub
parent 60c6c0959e
commit a4c100e5fe
13 changed files with 110 additions and 14 deletions
@@ -2,7 +2,7 @@ import { cn } from "cn";
import { Skeleton } from "#/components/Skeleton/Skeleton";
type PaginationHeaderProps = {
paginationUnitLabel: string;
paginationUnitLabel: { singular: string; plural: string };
limit: number;
totalRecords: number | undefined;
currentOffsetStart: number | undefined;
@@ -42,7 +42,7 @@ export const PaginationAmount: React.FC<PaginationHeaderProps> = ({
{totalRecords === 0 && (
<div>
{isFiltered
? `No ${paginationUnitLabel} match your search.`
? `No ${paginationUnitLabel.plural} match your search.`
: "No records available"}
</div>
)}
@@ -64,7 +64,9 @@ export const PaginationAmount: React.FC<PaginationHeaderProps> = ({
{totalRecords.toLocaleString()}
{countIsCapped && "+"}
</strong>{" "}
{paginationUnitLabel}
{totalRecords === 1 && !countIsCapped
? paginationUnitLabel.singular
: paginationUnitLabel.plural}
</div>
)}
</>
@@ -18,7 +18,7 @@ const meta: Meta<EssentialComponent> = {
title: "components/PaginationContainer",
component: PaginationContainer,
args: {
paginationUnitLabel: "puppies",
paginationUnitLabel: { singular: "puppy", plural: "puppies" },
children: <div>Put any content here</div>,
},
};
@@ -62,6 +62,32 @@ export const FirstPageWithLittleData: Story = {
},
};
export const SingleResult: Story = {
args: {
query: {
...mockPaginationResultBase,
currentOffsetStart: 1,
totalRecords: 1,
totalPages: 1,
isPlaceholderData: false,
},
},
};
export const LastPageWithOneResult: Story = {
args: {
query: {
...mockPaginationResultBase,
currentPage: 2,
currentOffsetStart: 26,
totalRecords: 26,
totalPages: 2,
hasPreviousPage: true,
isPlaceholderData: false,
},
},
};
export const FirstPageWithNoData: Story = {
args: {
query: {
@@ -124,6 +150,20 @@ export const SecondPageWithData: Story = {
},
};
export const CappedCountOfOne: Story = {
args: {
query: {
...mockPaginationResultBase,
currentOffsetStart: 1,
totalRecords: 1,
totalPages: 1,
hasNextPage: true,
isPlaceholderData: false,
countIsCapped: true,
},
},
};
export const CappedCountFirstPage: Story = {
args: {
query: {
@@ -9,7 +9,9 @@ export type PaginationResult<Data = unknown> = PaginationResultInfo & {
type PaginationProps = React.ComponentProps<"div"> & {
query: PaginationResult;
paginationUnitLabel: string;
paginationUnitLabel: React.ComponentProps<
typeof PaginationAmount
>["paginationUnitLabel"];
};
export const PaginationContainer: React.FC<PaginationProps> = ({
@@ -89,7 +89,10 @@ export const ListSessionsPageView: React.FC<ListSessionsPageViewProps> = ({
<>
<ListSessionsFilter {...filterProps} />
<PaginationContainer query={sessionsQuery} paginationUnitLabel="sessions">
<PaginationContainer
query={sessionsQuery}
paginationUnitLabel={{ singular: "session", plural: "sessions" }}
>
<Table className="text-sm font-normal">
<TableHeader>
<TableRow>
@@ -81,7 +81,10 @@ export const SpendUsersTable: React.FC<SpendUsersTableProps> = ({
</div>
)}
<SpendTotal report={report} />
<PaginationContainer query={reportQuery} paginationUnitLabel="users">
<PaginationContainer
query={reportQuery}
paginationUnitLabel={{ singular: "user", plural: "users" }}
>
<Table
aria-label="Spend by user"
className="table-fixed"
+1 -1
View File
@@ -69,7 +69,7 @@ export const AuditPageView: React.FC<AuditPageViewProps> = ({
<PaginationContainer
query={paginationResult}
paginationUnitLabel="logs"
paginationUnitLabel={{ singular: "log", plural: "logs" }}
>
<Table>
<TableBody>
@@ -70,7 +70,7 @@ export const ConnectionLogPageView: React.FC<ConnectionLogPageViewProps> = ({
<PaginationContainer
query={paginationResult}
paginationUnitLabel="logs"
paginationUnitLabel={{ singular: "log", plural: "logs" }}
>
<Table>
<TableBody>
@@ -127,7 +127,10 @@ const GroupMembersPage: React.FC = () => {
<div className="flex flex-col w-full gap-1 pb-8">
<UsersFilter {...filterProps} />
<PaginationContainer query={membersQuery} paginationUnitLabel="members">
<PaginationContainer
query={membersQuery}
paginationUnitLabel={{ singular: "member", plural: "members" }}
>
<Table aria-label="Group members">
<TableHeader>
<TableRow>
+4 -1
View File
@@ -138,7 +138,10 @@ export const GroupsPageView: React.FC<GroupsPageViewProps> = ({
<div className="flex flex-col gap-4">
<GroupsFilter {...filterProps} />
<PaginationContainer query={groupsQuery} paginationUnitLabel="groups">
<PaginationContainer
query={groupsQuery}
paginationUnitLabel={{ singular: "group", plural: "groups" }}
>
<Table aria-label="Groups">
<TableHeader>
<TableRow>
@@ -68,7 +68,10 @@ export const OrganizationMembersPageView: React.FC<
</p>
</div>
)}
<PaginationContainer query={membersQuery} paginationUnitLabel="members">
<PaginationContainer
query={membersQuery}
paginationUnitLabel={{ singular: "member", plural: "members" }}
>
<OrganizationMembersTable {...props} />
</PaginationContainer>
</div>
+4 -1
View File
@@ -55,7 +55,10 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
<UsersFilter {...filterProps} />
<PaginationContainer query={usersQuery} paginationUnitLabel="users">
<PaginationContainer
query={usersQuery}
paginationUnitLabel={{ singular: "user", plural: "users" }}
>
<UsersTable
{...props}
users={usersQuery.data?.users}
@@ -252,6 +252,37 @@ export const FilteredPaginationSummary: Story = {
},
};
export const FilteredSingleResult: Story = {
args: {
workspaces: [MockWorkspace],
count: 1,
},
};
export const UnfilteredSingleResult: Story = {
args: {
workspaces: [MockWorkspace],
count: 1,
filter: { ...defaultFilter, query: "", used: false },
},
};
export const LastPageWithOneResult: Story = {
args: {
workspaces: [MockWorkspace],
count: DEFAULT_RECORDS_PER_PAGE + 1,
page: 2,
},
};
export const UnfilteredEmptySummary: Story = {
args: {
workspaces: [],
count: 0,
filter: { ...defaultFilter, query: "", used: false },
},
};
export const FilteredEmptySummary: Story = {
args: {
workspaces: [],
@@ -179,7 +179,10 @@ export const WorkspacesPageView: React.FC<WorkspacesPageViewProps> = ({
) : (
!pageNumberIsInvalid && (
<PaginationAmount
paginationUnitLabel="workspaces"
paginationUnitLabel={{
singular: "workspace",
plural: "workspaces",
}}
limit={limit}
totalRecords={count}
currentOffsetStart={(page - 1) * limit + 1}