Skip to content
This repository was archived by the owner on Sep 30, 2025. It is now read-only.

Commit 18b37f8

Browse files
mattkubejmrcthms
authored andcommitted
Introduce pagination prop to tables and lists (#10633)
Supports Shopify/web#102860 `Pagination` is added as an auxiliary component to `DataTable`, `IndexTable`, and `ResourceList`. This has resulted in inconsistent usage and UX across Admin along with differing implementations. Baking this concern into the resources will provide a consistent aesthetic and ease our maintainability of pagination concerns with regards to these components. At the moment, `web` (i.e. Admin) has the following instance counts with `Pagination`: - DataTable - 8 - IndexTable - 34 - ResourceList - 25 There also includes 11 `ResourceListWithHeader` instances, but that is a custom component within Admin. So, maintaining a consistent Pagination experience across each of these instances becomes challenging if they each need independent instantiation of `Pagination` as a child component. ❓ For `IndexTable` should I key of [hasMoreItems](https://github.com/Shopify/polaris/blob/557134c75c8f5b6dee0d925561e9a2b4ba900b2b/polaris-react/src/components/IndexTable/IndexTable.tsx#L1184) to determine whether to render `Pagination` or should the consumer manage it? Need to better understand what this attribute is used for and whether that's reasonable. Edit: This does not seem to be consistently used, so I don't think this would be reliable. 🚨 **REMINDER for myself:** Update documentation with regards to `Pagination` and `IndexTable` Introduces an optional `pagination` prop to `DataTable`, `IndexTable`, and `ResourceList`. If the `pagination` prop is provided, which contains the `Pagination` props, then it will render a table type `Pagination` component at the bottom of the of the table/list. If no `pagination` prop is provided, then it will not render a `Pagination` component. The table type `Pagination` component will also have a border top by default. We do not have a use case at the moment where a table type `Pagination` component does not need this aesthetic, so it is always present. Increases table type `Pagination` buttons to a larger click area (i.e. `28px`). - [Pagination](https://storybook.web.polaris-table-component-pagination.matt-kubej.us.spin.dev/?path=/story/all-components-pagination--with-table-type&globals=polarisSummerEditions2023:true;polarisSummerEditions2023ShadowBevelOptOut:true) - [DataTable](https://storybook.web.polaris-table-component-pagination.matt-kubej.us.spin.dev/?path=/story/all-components-datatable--with-pagination&globals=polarisSummerEditions2023:true;polarisSummerEditions2023ShadowBevelOptOut:true) - [IndexTable](https://storybook.web.polaris-table-component-pagination.matt-kubej.us.spin.dev/?path=/story/all-components-indextable--with-pagination&globals=polarisSummerEditions2023:true;polarisSummerEditions2023ShadowBevelOptOut:true) - [ResourceList](https://storybook.web.polaris-table-component-pagination.matt-kubej.us.spin.dev/?path=/story/all-components-resourcelist--with-pagination&globals=polarisSummerEditions2023:true;polarisSummerEditions2023ShadowBevelOptOut:true) - [ ] Tested on [mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing) - [ ] Tested on [multiple browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers) - [ ] Tested for [accessibility](https://github.com/Shopify/polaris/blob/main/documentation/Accessibility%20testing.md) - [ ] Updated the component's `README.md` with documentation changes - [ ] [Tophatted documentation](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting%20documentation.md) changes in the style guide --------- Co-authored-by: Marc Thomas <marc.thomas@shopify.com>
1 parent 20e5b3a commit 18b37f8

File tree

18 files changed

+561
-15
lines changed

18 files changed

+561
-15
lines changed

.changeset/silver-wasps-greet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/polaris': minor
3+
---
4+
5+
Updated IndexTable, ResourceList, and DataTable to have built-in pagination props

polaris-react/src/components/DataTable/DataTable.stories.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,3 +874,46 @@ export function WithStickyHeaderEnabled() {
874874
</Page>
875875
);
876876
}
877+
878+
export function WithPagination() {
879+
const rows = [
880+
['Emerald Silk Gown', '$875.00', 124689, 140, '$122,500.00'],
881+
['Mauve Cashmere Scarf', '$230.00', 124533, 83, '$19,090.00'],
882+
[
883+
'Navy Merino Wool Blazer with khaki chinos and yellow belt',
884+
'$445.00',
885+
124518,
886+
32,
887+
'$14,240.00',
888+
],
889+
];
890+
891+
return (
892+
<Page title="Sales by product">
893+
<LegacyCard>
894+
<DataTable
895+
columnContentTypes={[
896+
'text',
897+
'numeric',
898+
'numeric',
899+
'numeric',
900+
'numeric',
901+
]}
902+
headings={[
903+
'Product',
904+
'Price',
905+
'SKU Number',
906+
'Net quantity',
907+
'Net sales',
908+
]}
909+
rows={rows}
910+
totals={['', '', '', 255, '$155,830.00']}
911+
pagination={{
912+
hasNext: true,
913+
onNext: () => {},
914+
}}
915+
/>
916+
</LegacyCard>
917+
</Page>
918+
);
919+
}

polaris-react/src/components/DataTable/DataTable.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {headerCell} from '../shared';
1010
import {EventListener} from '../EventListener';
1111
import {AfterInitialMount} from '../AfterInitialMount';
1212
import {Sticky} from '../Sticky';
13+
import {Pagination} from '../Pagination';
14+
import type {PaginationProps} from '../Pagination';
1315

1416
import {Cell, Navigation} from './components';
1517
import type {CellProps} from './components';
@@ -28,6 +30,8 @@ export type TableData = string | number | React.ReactNode;
2830

2931
export type ColumnContentType = 'text' | 'numeric';
3032

33+
export type DataTablePaginationProps = Omit<PaginationProps, 'type'>;
34+
3135
const getRowClientHeights = (rows: NodeList | undefined) => {
3236
const heights: number[] = [];
3337
if (!rows) {
@@ -97,6 +101,8 @@ export interface DataTableProps {
97101
fixedFirstColumns?: number;
98102
/** Specify a min width for the first column if neccessary */
99103
firstColumnMinWidth?: string;
104+
/** Properties to enable pagination at the bottom of the table. */
105+
pagination?: DataTablePaginationProps;
100106
}
101107

102108
type CombinedProps = DataTableProps & {
@@ -177,6 +183,7 @@ class DataTableInner extends PureComponent<CombinedProps, DataTableState> {
177183
hasZebraStripingOnData = false,
178184
stickyHeader = false,
179185
hasFixedFirstColumn: fixedFirstColumn = false,
186+
pagination,
180187
} = this.props;
181188
const {
182189
condensed,
@@ -300,6 +307,10 @@ class DataTableInner extends PureComponent<CombinedProps, DataTableState> {
300307
<div className={styles.Footer}>{footerContent}</div>
301308
) : null;
302309

310+
const paginationMarkup = pagination ? (
311+
<Pagination type="table" {...pagination} />
312+
) : null;
313+
303314
const headerTotalsMarkup = !showTotalsInFooter ? totalsMarkup : null;
304315
const footerTotalsMarkup = showTotalsInFooter ? (
305316
<tfoot>{totalsMarkup}</tfoot>
@@ -398,6 +409,7 @@ class DataTableInner extends PureComponent<CombinedProps, DataTableState> {
398409
{footerTotalsMarkup}
399410
</table>
400411
</div>
412+
{paginationMarkup}
401413
{footerMarkup}
402414
</div>
403415
</div>

polaris-react/src/components/DataTable/tests/DataTable.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {timer} from '@shopify/jest-dom-mocks';
33
import {mountWithApp} from 'tests/utilities';
44

55
import {Checkbox} from '../../Checkbox';
6+
import {Pagination} from '../../Pagination';
67
import {Cell, Navigation} from '../components';
78
import {DataTable} from '../DataTable';
89
import type {DataTableProps} from '../DataTable';
@@ -639,4 +640,27 @@ describe('<DataTable />', () => {
639640
expect(tables).toHaveLength(1);
640641
});
641642
});
643+
644+
describe('pagination', () => {
645+
it('does not render Pagination when pagination props are not provided', () => {
646+
const dataTable = mountWithApp(<DataTable {...defaultProps} />);
647+
648+
expect(dataTable).not.toContainReactComponent(Pagination);
649+
});
650+
651+
it('renders Pagination with table type when pagination props are provided', () => {
652+
const paginationProps = {
653+
hasNext: true,
654+
};
655+
656+
const dataTable = mountWithApp(
657+
<DataTable {...defaultProps} pagination={paginationProps} />,
658+
);
659+
660+
expect(dataTable).toContainReactComponent(Pagination, {
661+
type: 'table',
662+
...paginationProps,
663+
});
664+
});
665+
});
642666
});

polaris-react/src/components/IndexTable/IndexTable.stories.tsx

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,7 +1799,6 @@ export function WithStickyLastColumn() {
17991799
{amountSpent}
18001800
</Text>
18011801
</IndexTable.Cell>
1802-
<IndexTable.Cell>{status}</IndexTable.Cell>
18031802
<IndexTable.Cell>{channel}</IndexTable.Cell>
18041803
<IndexTable.Cell>{paymentStatus}</IndexTable.Cell>
18051804
<IndexTable.Cell>{fulfillmentStatus}</IndexTable.Cell>
@@ -1829,7 +1828,6 @@ export function WithStickyLastColumn() {
18291828
id: 'amount-spent',
18301829
title: 'Amount spent',
18311830
},
1832-
{title: 'Tone'},
18331831
{title: 'Channel'},
18341832
{title: 'Payment status'},
18351833
{title: 'Fulfillment status'},
@@ -4003,6 +4001,119 @@ export function WithSubHeaders() {
40034001
);
40044002
}
40054003

4004+
export function WithPagination() {
4005+
const customers = [
4006+
{
4007+
id: '3410',
4008+
url: '#',
4009+
name: 'Mae Jemison',
4010+
location: 'Decatur, USA',
4011+
orders: 20,
4012+
amountSpent: '$2,400',
4013+
},
4014+
{
4015+
id: '3411',
4016+
url: '#',
4017+
name: 'Joe Jemison',
4018+
location: 'Sydney, AU',
4019+
orders: 20,
4020+
amountSpent: '$1,400',
4021+
},
4022+
{
4023+
id: '3412',
4024+
url: '#',
4025+
name: 'Sam Jemison',
4026+
location: 'Decatur, USA',
4027+
orders: 20,
4028+
amountSpent: '$400',
4029+
},
4030+
{
4031+
id: '3413',
4032+
url: '#',
4033+
name: 'Mae Jemison',
4034+
location: 'Decatur, USA',
4035+
orders: 20,
4036+
amountSpent: '$4,300',
4037+
},
4038+
{
4039+
id: '2563',
4040+
url: '#',
4041+
name: 'Ellen Ochoa',
4042+
location: 'Los Angeles, USA',
4043+
orders: 30,
4044+
amountSpent: '$140',
4045+
},
4046+
];
4047+
const resourceName = {
4048+
singular: 'customer',
4049+
plural: 'customers',
4050+
};
4051+
4052+
const {selectedResources, allResourcesSelected, handleSelectionChange} =
4053+
useIndexResourceState(customers);
4054+
4055+
const rowMarkup = customers.map(
4056+
({id, name, location, orders, amountSpent}, index) => (
4057+
<IndexTable.Row
4058+
id={id}
4059+
key={id}
4060+
selected={selectedResources.includes(id)}
4061+
position={index}
4062+
>
4063+
<IndexTable.Cell>
4064+
<Text fontWeight="bold" as="span">
4065+
{name}
4066+
</Text>
4067+
</IndexTable.Cell>
4068+
<IndexTable.Cell>{location}</IndexTable.Cell>
4069+
<IndexTable.Cell>
4070+
<Text as="span" alignment="end" numeric>
4071+
{orders}
4072+
</Text>
4073+
</IndexTable.Cell>
4074+
<IndexTable.Cell>
4075+
<Text as="span" alignment="end" numeric>
4076+
{amountSpent}
4077+
</Text>
4078+
</IndexTable.Cell>
4079+
</IndexTable.Row>
4080+
),
4081+
);
4082+
4083+
return (
4084+
<LegacyCard>
4085+
<IndexTable
4086+
resourceName={resourceName}
4087+
itemCount={customers.length}
4088+
selectedItemsCount={
4089+
allResourcesSelected ? 'All' : selectedResources.length
4090+
}
4091+
onSelectionChange={handleSelectionChange}
4092+
headings={[
4093+
{title: 'Name'},
4094+
{title: 'Location'},
4095+
{
4096+
alignment: 'end',
4097+
id: 'order-count',
4098+
title: 'Order count',
4099+
},
4100+
{
4101+
alignment: 'end',
4102+
id: 'amount-spent',
4103+
title: 'Amount spent',
4104+
},
4105+
]}
4106+
pagination={{
4107+
hasNext: true,
4108+
onNext: () => {},
4109+
}}
4110+
>
4111+
{rowMarkup}
4112+
</IndexTable>
4113+
</LegacyCard>
4114+
);
4115+
}
4116+
40064117
export function WithSubHeadersNonSelectable() {
40074118
const rows = [
40084119
{

polaris-react/src/components/IndexTable/IndexTable.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {EventListener} from '../EventListener';
1515
import {SelectAllActions} from '../SelectAllActions';
1616
// eslint-disable-next-line import/no-deprecated
1717
import {LegacyStack} from '../LegacyStack';
18+
import {Pagination} from '../Pagination';
19+
import type {PaginationProps} from '../Pagination';
1820
import {Sticky} from '../Sticky';
1921
import {Spinner} from '../Spinner';
2022
import {Text} from '../Text';
@@ -89,6 +91,8 @@ interface IndexTableSortToggleLabels {
8991
[key: number]: IndexTableSortToggleLabel;
9092
}
9193

94+
export type IndexTablePaginationProps = Omit<PaginationProps, 'type'>;
95+
9296
export interface IndexTableBaseProps {
9397
headings: NonEmptyArray<IndexTableHeading>;
9498
promotedBulkActions?: BulkActionsProps['promotedActions'];
@@ -119,6 +123,8 @@ export interface IndexTableBaseProps {
119123
sortToggleLabels?: IndexTableSortToggleLabels;
120124
/** Add zebra striping to table rows */
121125
hasZebraStriping?: boolean;
126+
/** Properties to enable pagination at the bottom of the table. */
127+
pagination?: IndexTablePaginationProps;
122128
}
123129

124130
export interface TableHeadingRect {
@@ -1165,21 +1171,29 @@ export function IndexTable({
11651171
hasMoreItems,
11661172
condensed,
11671173
onSelectionChange,
1174+
pagination,
11681175
...indexTableBaseProps
11691176
}: IndexTableProps) {
1177+
const paginationMarkup = pagination ? (
1178+
<Pagination type="table" {...pagination} />
1179+
) : null;
1180+
11701181
return (
1171-
<IndexProvider
1172-
selectable={selectable && !condensed}
1173-
itemCount={itemCount}
1174-
selectedItemsCount={selectedItemsCount}
1175-
resourceName={passedResourceName}
1176-
loading={loading}
1177-
hasMoreItems={hasMoreItems}
1178-
condensed={condensed}
1179-
onSelectionChange={onSelectionChange}
1180-
>
1181-
<IndexTableBase {...indexTableBaseProps}>{children}</IndexTableBase>
1182-
</IndexProvider>
1182+
<>
1183+
<IndexProvider
1184+
selectable={selectable && !condensed}
1185+
itemCount={itemCount}
1186+
selectedItemsCount={selectedItemsCount}
1187+
resourceName={passedResourceName}
1188+
loading={loading}
1189+
hasMoreItems={hasMoreItems}
1190+
condensed={condensed}
1191+
onSelectionChange={onSelectionChange}
1192+
>
1193+
<IndexTableBase {...indexTableBaseProps}>{children}</IndexTableBase>
1194+
</IndexProvider>
1195+
{paginationMarkup}
1196+
</>
11831197
);
11841198
}
11851199

polaris-react/src/components/IndexTable/tests/IndexTable.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {AfterInitialMount} from '../../AfterInitialMount';
2222
import {UnstyledButton} from '../../UnstyledButton';
2323
import {Tooltip} from '../../Tooltip';
2424
import {IndexProvider} from '../../IndexProvider';
25+
import {Pagination} from '../../Pagination';
2526

2627
jest.mock('../utilities', () => ({
2728
...jest.requireActual('../utilities'),
@@ -802,4 +803,33 @@ describe('<IndexTable>', () => {
802803
expect(computeTableDimensions).toHaveBeenCalledTimes(2);
803804
});
804805
});
806+
807+
describe('pagination', () => {
808+
it('does not render Pagination when pagination props are not provided', () => {
809+
const index = mountWithApp(
810+
<IndexTable {...defaultProps} pagination={undefined}>
811+
{mockTableItems.map(mockRenderRow)}
812+
</IndexTable>,
813+
);
814+
815+
expect(index).not.toContainReactComponent(Pagination);
816+
});
817+
818+
it('renders Pagination with table type when pagination props are provided', () => {
819+
const paginationProps = {
820+
hasNext: true,
821+
};
822+
823+
const index = mountWithApp(
824+
<IndexTable {...defaultProps} pagination={paginationProps}>
825+
{mockTableItems.map(mockRenderRow)}
826+
</IndexTable>,
827+
);
828+
829+
expect(index).toContainReactComponent(Pagination, {
830+
type: 'table',
831+
...paginationProps,
832+
});
833+
});
834+
});
805835
});

polaris-react/src/components/Pagination/Pagination.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
}
2525

2626
&.table {
27+
border-top: 1px solid var(--p-color-border);
28+
2729
button {
2830
--button-min-height: var(--p-space-600);
2931
background-color: var(--p-color-bg-surface-secondary);

0 commit comments

Comments
 (0)