diff --git a/.changeset/silver-wasps-greet.md b/.changeset/silver-wasps-greet.md
new file mode 100644
index 00000000000..c81d001e472
--- /dev/null
+++ b/.changeset/silver-wasps-greet.md
@@ -0,0 +1,5 @@
+---
+'@shopify/polaris': minor
+---
+
+Updated IndexTable, ResourceList, and DataTable to have built-in pagination props
diff --git a/polaris-react/src/components/DataTable/DataTable.stories.tsx b/polaris-react/src/components/DataTable/DataTable.stories.tsx
index 0f0558d20cf..0f111a97ce5 100644
--- a/polaris-react/src/components/DataTable/DataTable.stories.tsx
+++ b/polaris-react/src/components/DataTable/DataTable.stories.tsx
@@ -874,3 +874,46 @@ export function WithStickyHeaderEnabled() {
);
}
+
+export function WithPagination() {
+ const rows = [
+ ['Emerald Silk Gown', '$875.00', 124689, 140, '$122,500.00'],
+ ['Mauve Cashmere Scarf', '$230.00', 124533, 83, '$19,090.00'],
+ [
+ 'Navy Merino Wool Blazer with khaki chinos and yellow belt',
+ '$445.00',
+ 124518,
+ 32,
+ '$14,240.00',
+ ],
+ ];
+
+ return (
+
+
+ {},
+ }}
+ />
+
+
+ );
+}
diff --git a/polaris-react/src/components/DataTable/DataTable.tsx b/polaris-react/src/components/DataTable/DataTable.tsx
index 5dbff6297e4..fb6bd624a40 100644
--- a/polaris-react/src/components/DataTable/DataTable.tsx
+++ b/polaris-react/src/components/DataTable/DataTable.tsx
@@ -10,6 +10,8 @@ import {headerCell} from '../shared';
import {EventListener} from '../EventListener';
import {AfterInitialMount} from '../AfterInitialMount';
import {Sticky} from '../Sticky';
+import {Pagination} from '../Pagination';
+import type {PaginationProps} from '../Pagination';
import {Cell, Navigation} from './components';
import type {CellProps} from './components';
@@ -28,6 +30,8 @@ export type TableData = string | number | React.ReactNode;
export type ColumnContentType = 'text' | 'numeric';
+export type DataTablePaginationProps = Omit;
+
const getRowClientHeights = (rows: NodeList | undefined) => {
const heights: number[] = [];
if (!rows) {
@@ -97,6 +101,8 @@ export interface DataTableProps {
fixedFirstColumns?: number;
/** Specify a min width for the first column if neccessary */
firstColumnMinWidth?: string;
+ /** Properties to enable pagination at the bottom of the table. */
+ pagination?: DataTablePaginationProps;
}
type CombinedProps = DataTableProps & {
@@ -177,6 +183,7 @@ class DataTableInner extends PureComponent {
hasZebraStripingOnData = false,
stickyHeader = false,
hasFixedFirstColumn: fixedFirstColumn = false,
+ pagination,
} = this.props;
const {
condensed,
@@ -300,6 +307,10 @@ class DataTableInner extends PureComponent {
{footerContent}
) : null;
+ const paginationMarkup = pagination ? (
+
+ ) : null;
+
const headerTotalsMarkup = !showTotalsInFooter ? totalsMarkup : null;
const footerTotalsMarkup = showTotalsInFooter ? (
{totalsMarkup}
@@ -398,6 +409,7 @@ class DataTableInner extends PureComponent {
{footerTotalsMarkup}
+ {paginationMarkup}
{footerMarkup}
diff --git a/polaris-react/src/components/DataTable/tests/DataTable.test.tsx b/polaris-react/src/components/DataTable/tests/DataTable.test.tsx
index 1ad973222ef..3bb16813c1b 100644
--- a/polaris-react/src/components/DataTable/tests/DataTable.test.tsx
+++ b/polaris-react/src/components/DataTable/tests/DataTable.test.tsx
@@ -3,6 +3,7 @@ import {timer} from '@shopify/jest-dom-mocks';
import {mountWithApp} from 'tests/utilities';
import {Checkbox} from '../../Checkbox';
+import {Pagination} from '../../Pagination';
import {Cell, Navigation} from '../components';
import {DataTable} from '../DataTable';
import type {DataTableProps} from '../DataTable';
@@ -639,4 +640,27 @@ describe('', () => {
expect(tables).toHaveLength(1);
});
});
+
+ describe('pagination', () => {
+ it('does not render Pagination when pagination props are not provided', () => {
+ const dataTable = mountWithApp();
+
+ expect(dataTable).not.toContainReactComponent(Pagination);
+ });
+
+ it('renders Pagination with table type when pagination props are provided', () => {
+ const paginationProps = {
+ hasNext: true,
+ };
+
+ const dataTable = mountWithApp(
+ ,
+ );
+
+ expect(dataTable).toContainReactComponent(Pagination, {
+ type: 'table',
+ ...paginationProps,
+ });
+ });
+ });
});
diff --git a/polaris-react/src/components/IndexTable/IndexTable.stories.tsx b/polaris-react/src/components/IndexTable/IndexTable.stories.tsx
index 3273ac0f2cd..df4627eae0d 100644
--- a/polaris-react/src/components/IndexTable/IndexTable.stories.tsx
+++ b/polaris-react/src/components/IndexTable/IndexTable.stories.tsx
@@ -3929,3 +3929,116 @@ export function WithSubHeaders() {
);
}
+
+export function WithPagination() {
+ const customers = [
+ {
+ id: '3410',
+ url: '#',
+ name: 'Mae Jemison',
+ location: 'Decatur, USA',
+ orders: 20,
+ amountSpent: '$2,400',
+ },
+ {
+ id: '3411',
+ url: '#',
+ name: 'Joe Jemison',
+ location: 'Sydney, AU',
+ orders: 20,
+ amountSpent: '$1,400',
+ },
+ {
+ id: '3412',
+ url: '#',
+ name: 'Sam Jemison',
+ location: 'Decatur, USA',
+ orders: 20,
+ amountSpent: '$400',
+ },
+ {
+ id: '3413',
+ url: '#',
+ name: 'Mae Jemison',
+ location: 'Decatur, USA',
+ orders: 20,
+ amountSpent: '$4,300',
+ },
+ {
+ id: '2563',
+ url: '#',
+ name: 'Ellen Ochoa',
+ location: 'Los Angeles, USA',
+ orders: 30,
+ amountSpent: '$140',
+ },
+ ];
+ const resourceName = {
+ singular: 'customer',
+ plural: 'customers',
+ };
+
+ const {selectedResources, allResourcesSelected, handleSelectionChange} =
+ useIndexResourceState(customers);
+
+ const rowMarkup = customers.map(
+ ({id, name, location, orders, amountSpent}, index) => (
+
+
+
+ {name}
+
+
+ {location}
+
+
+ {orders}
+
+
+
+
+ {amountSpent}
+
+
+
+ ),
+ );
+
+ return (
+
+ {},
+ }}
+ >
+ {rowMarkup}
+
+
+ );
+}
diff --git a/polaris-react/src/components/IndexTable/IndexTable.tsx b/polaris-react/src/components/IndexTable/IndexTable.tsx
index db7276dd631..75c0c5b007e 100644
--- a/polaris-react/src/components/IndexTable/IndexTable.tsx
+++ b/polaris-react/src/components/IndexTable/IndexTable.tsx
@@ -15,6 +15,8 @@ import {EventListener} from '../EventListener';
import {SelectAllActions} from '../SelectAllActions';
// eslint-disable-next-line import/no-deprecated
import {LegacyStack} from '../LegacyStack';
+import {Pagination} from '../Pagination';
+import type {PaginationProps} from '../Pagination';
import {Sticky} from '../Sticky';
import {Spinner} from '../Spinner';
import {Text} from '../Text';
@@ -88,6 +90,8 @@ interface IndexTableSortToggleLabels {
[key: number]: IndexTableSortToggleLabel;
}
+export type IndexTablePaginationProps = Omit;
+
export interface IndexTableBaseProps {
headings: NonEmptyArray;
promotedBulkActions?: BulkActionsProps['promotedActions'];
@@ -118,6 +122,8 @@ export interface IndexTableBaseProps {
sortToggleLabels?: IndexTableSortToggleLabels;
/** Add zebra striping to table rows */
hasZebraStriping?: boolean;
+ /** Properties to enable pagination at the bottom of the table. */
+ pagination?: IndexTablePaginationProps;
}
export interface TableHeadingRect {
@@ -1162,21 +1168,29 @@ export function IndexTable({
hasMoreItems,
condensed,
onSelectionChange,
+ pagination,
...indexTableBaseProps
}: IndexTableProps) {
+ const paginationMarkup = pagination ? (
+
+ ) : null;
+
return (
-
- {children}
-
+ <>
+
+ {children}
+
+ {paginationMarkup}
+ >
);
}
diff --git a/polaris-react/src/components/IndexTable/tests/IndexTable.test.tsx b/polaris-react/src/components/IndexTable/tests/IndexTable.test.tsx
index 7478a84185a..61801781d38 100644
--- a/polaris-react/src/components/IndexTable/tests/IndexTable.test.tsx
+++ b/polaris-react/src/components/IndexTable/tests/IndexTable.test.tsx
@@ -22,6 +22,7 @@ import {AfterInitialMount} from '../../AfterInitialMount';
import {UnstyledButton} from '../../UnstyledButton';
import {Tooltip} from '../../Tooltip';
import {IndexProvider} from '../../IndexProvider';
+import {Pagination} from '../../Pagination';
jest.mock('../utilities', () => ({
...jest.requireActual('../utilities'),
@@ -802,4 +803,33 @@ describe('', () => {
expect(computeTableDimensions).toHaveBeenCalledTimes(2);
});
});
+
+ describe('pagination', () => {
+ it('does not render Pagination when pagination props are not provided', () => {
+ const index = mountWithApp(
+
+ {mockTableItems.map(mockRenderRow)}
+ ,
+ );
+
+ expect(index).not.toContainReactComponent(Pagination);
+ });
+
+ it('renders Pagination with table type when pagination props are provided', () => {
+ const paginationProps = {
+ hasNext: true,
+ };
+
+ const index = mountWithApp(
+
+ {mockTableItems.map(mockRenderRow)}
+ ,
+ );
+
+ expect(index).toContainReactComponent(Pagination, {
+ type: 'table',
+ ...paginationProps,
+ });
+ });
+ });
});
diff --git a/polaris-react/src/components/Pagination/Pagination.scss b/polaris-react/src/components/Pagination/Pagination.scss
index 6e3c3d4cc9e..e3ac706d98f 100644
--- a/polaris-react/src/components/Pagination/Pagination.scss
+++ b/polaris-react/src/components/Pagination/Pagination.scss
@@ -24,8 +24,10 @@
}
&.table {
+ border-top: 1px solid var(--p-color-border);
+
button {
- --button-min-height: var(--p-space-600);
+ --button-min-height: 28px;
background-color: var(--p-color-bg-subdued);
min-height: var(--button-min-height);
min-width: var(--button-min-height);
diff --git a/polaris-react/src/components/Pagination/Pagination.stories.tsx b/polaris-react/src/components/Pagination/Pagination.stories.tsx
index f8a235c65e9..a547bb90437 100644
--- a/polaris-react/src/components/Pagination/Pagination.stories.tsx
+++ b/polaris-react/src/components/Pagination/Pagination.stories.tsx
@@ -64,7 +64,6 @@ export function WithTableType() {
style={{
maxWidth: 'calc(700px + (2 * var(--p-space-400)))',
margin: '0 auto',
- border: '1px solid var(--p-color-border)',
}}
>
+ {},
+ }}
+ renderItem={(item) => {
+ const {id, url, name, location} = item;
+ const media = ;
+
+ return (
+
+
+
+ {name}
+
+
+ {location}
+
+ );
+ }}
+ />
+
+ );
+}
diff --git a/polaris-react/src/components/ResourceList/ResourceList.tsx b/polaris-react/src/components/ResourceList/ResourceList.tsx
index 3b2eed60686..ab00d9d3c16 100644
--- a/polaris-react/src/components/ResourceList/ResourceList.tsx
+++ b/polaris-react/src/components/ResourceList/ResourceList.tsx
@@ -31,6 +31,8 @@ import {BulkActions, useIsBulkActionsSticky} from '../BulkActions';
import type {BulkActionsProps} from '../BulkActions';
import {SelectAllActions} from '../SelectAllActions';
import {CheckableButton} from '../CheckableButton';
+import {Pagination} from '../Pagination';
+import type {PaginationProps} from '../Pagination';
import styles from './ResourceList.scss';
@@ -66,6 +68,8 @@ function defaultIdForItem(
: index.toString();
}
+export type ResourceListPaginationProps = Omit;
+
export interface ResourceListProps<
TItemType extends ResourceListItemData = ResourceListItemData,
> {
@@ -121,6 +125,8 @@ export interface ResourceListProps<
idForItem?(item: TItemType, index: number): string;
/** Function to resolve the ids of items */
resolveItemId?(item: TItemType): string;
+ /** Properties to enable pagination at the bottom of the list. */
+ pagination?: ResourceListPaginationProps;
}
export function ResourceList({
@@ -148,6 +154,7 @@ export function ResourceList({
renderItem,
idForItem = defaultIdForItem,
resolveItemId,
+ pagination,
}: ResourceListProps) {
const i18n = useI18n();
const [selectMode, setSelectMode] = useState(
@@ -737,6 +744,10 @@ export function ResourceList({
) : null;
+ const paginationMarkup = pagination ? (
+
+ ) : null;
+
// This is probably a legit error but I don't have the time to refactor this
// eslint-disable-next-line react/jsx-no-constructed-context-values
const context = {
@@ -767,6 +778,7 @@ export function ResourceList({
{emptySearchStateMarkup}
{emptyStateMarkup}
{loadingWithoutItemsMarkup}
+ {paginationMarkup}
diff --git a/polaris-react/src/components/ResourceList/tests/ResourceList.test.tsx b/polaris-react/src/components/ResourceList/tests/ResourceList.test.tsx
index 280bed0a3e6..fa29ba97f37 100644
--- a/polaris-react/src/components/ResourceList/tests/ResourceList.test.tsx
+++ b/polaris-react/src/components/ResourceList/tests/ResourceList.test.tsx
@@ -13,6 +13,7 @@ import {EmptyState} from '../../EmptyState';
import {Select} from '../../Select';
import {Spinner} from '../../Spinner';
import {ResourceItem} from '../../ResourceItem';
+import {Pagination} from '../../Pagination';
import {SELECT_ALL_ITEMS} from '../../../utilities/resource-list';
import {ResourceList} from '../ResourceList';
import styles from '../ResourceList.scss';
@@ -1359,6 +1360,35 @@ describe('', () => {
expect(computeTableDimensions).toHaveBeenCalledTimes(2);
});
});
+
+ describe('pagination', () => {
+ it('does not render Pagination when pagination props are not provided', () => {
+ const resourceList = mountWithApp(
+ ,
+ );
+
+ expect(resourceList).not.toContainReactComponent(Pagination);
+ });
+
+ it('renders Pagination with table type when pagination props are provided', () => {
+ const paginationProps = {
+ hasNext: true,
+ };
+
+ const resourceList = mountWithApp(
+ ,
+ );
+
+ expect(resourceList).toContainReactComponent(Pagination, {
+ type: 'table',
+ ...paginationProps,
+ });
+ });
+ });
});
function noop() {}
diff --git a/polaris.shopify.com/content/components/lists/resource-list.md b/polaris.shopify.com/content/components/lists/resource-list.md
index 3c69a01f387..8e120502407 100644
--- a/polaris.shopify.com/content/components/lists/resource-list.md
+++ b/polaris.shopify.com/content/components/lists/resource-list.md
@@ -66,6 +66,9 @@ examples:
- fileName: resource-list-with-all-of-its-elements.tsx
title: With all of its elements
description: Use as a broad example that includes most props available to resource list.
+ - fileName: resource-list-with-pagination.tsx
+ title: With pagination
+ description: Use when the list contains many rows and they need paginating.
previewImg: /images/components/lists/resource-list.png
---
diff --git a/polaris.shopify.com/content/components/tables/data-table.md b/polaris.shopify.com/content/components/tables/data-table.md
index c2cb4e89838..c33aa92e7ce 100644
--- a/polaris.shopify.com/content/components/tables/data-table.md
+++ b/polaris.shopify.com/content/components/tables/data-table.md
@@ -42,6 +42,9 @@ examples:
- fileName: data-table-with-sticky-header-enabled.tsx
title: With sticky header enabled
description: Use as a broad example that includes most props available to data table.
+ - fileName: data-table-with-pagination.tsx
+ title: With pagination
+ description: Use when the table contains many rows and they need paginating.
previewImg: /images/components/tables/data-table.png
---
diff --git a/polaris.shopify.com/content/components/tables/index-table.md b/polaris.shopify.com/content/components/tables/index-table.md
index 64498ba1e4f..f4e6f727ef4 100644
--- a/polaris.shopify.com/content/components/tables/index-table.md
+++ b/polaris.shopify.com/content/components/tables/index-table.md
@@ -72,6 +72,9 @@ examples:
- fileName: index-table-with-subheaders.tsx
title: With subheaders
description: An index table with multiple table headers. Use to present merchants with resources grouped by a relevant data value to enable faster bulk selection.
+ - fileName: index-table-with-pagination.tsx
+ title: With pagination
+ description: Use when the table contains many rows and they need paginating.
previewImg: /images/components/tables/index-table.png
---
diff --git a/polaris.shopify.com/pages/examples/data-table-with-pagination.tsx b/polaris.shopify.com/pages/examples/data-table-with-pagination.tsx
new file mode 100644
index 00000000000..76613126f4c
--- /dev/null
+++ b/polaris.shopify.com/pages/examples/data-table-with-pagination.tsx
@@ -0,0 +1,48 @@
+import {Page, LegacyCard, DataTable} from '@shopify/polaris';
+import React from 'react';
+import {withPolarisExample} from '../../src/components/PolarisExampleWrapper';
+
+function DataTableWithPaginationExample() {
+ const rows = [
+ ['Emerald Silk Gown', '$875.00', 124689, 140, '$122,500.00'],
+ ['Mauve Cashmere Scarf', '$230.00', 124533, 83, '$19,090.00'],
+ [
+ 'Navy Merino Wool Blazer with khaki chinos and yellow belt',
+ '$445.00',
+ 124518,
+ 32,
+ '$14,240.00',
+ ],
+ ];
+
+ return (
+
+
+ {},
+ }}
+ />
+
+
+ );
+}
+
+export default withPolarisExample(DataTableWithPaginationExample);
diff --git a/polaris.shopify.com/pages/examples/index-table-with-pagination.tsx b/polaris.shopify.com/pages/examples/index-table-with-pagination.tsx
new file mode 100644
index 00000000000..b162de459eb
--- /dev/null
+++ b/polaris.shopify.com/pages/examples/index-table-with-pagination.tsx
@@ -0,0 +1,102 @@
+import {
+ IndexTable,
+ LegacyCard,
+ useIndexResourceState,
+ Text,
+ Badge,
+} from '@shopify/polaris';
+import React from 'react';
+import {withPolarisExample} from '../../src/components/PolarisExampleWrapper';
+
+function IndexTableWithPaginationExample() {
+ const orders = [
+ {
+ id: '1020',
+ order: '#1020',
+ date: 'Jul 20 at 4:34pm',
+ customer: 'Jaydon Stanton',
+ total: '$969.44',
+ paymentStatus: Paid,
+ fulfillmentStatus: Unfulfilled,
+ },
+ {
+ id: '1019',
+ order: '#1019',
+ date: 'Jul 20 at 3:46pm',
+ customer: 'Ruben Westerfelt',
+ total: '$701.19',
+ paymentStatus: Partially paid,
+ fulfillmentStatus: Unfulfilled,
+ },
+ {
+ id: '1018',
+ order: '#1018',
+ date: 'Jul 20 at 3.44pm',
+ customer: 'Leo Carder',
+ total: '$798.24',
+ paymentStatus: Paid,
+ fulfillmentStatus: Unfulfilled,
+ },
+ ];
+ const resourceName = {
+ singular: 'order',
+ plural: 'orders',
+ };
+
+ const {selectedResources, allResourcesSelected, handleSelectionChange} =
+ useIndexResourceState(orders);
+
+ const rowMarkup = orders.map(
+ (
+ {id, order, date, customer, total, paymentStatus, fulfillmentStatus},
+ index,
+ ) => (
+
+
+
+ {order}
+
+
+ {date}
+ {customer}
+ {total}
+ {paymentStatus}
+ {fulfillmentStatus}
+
+ ),
+ );
+
+ return (
+
+ {},
+ }}
+ >
+ {rowMarkup}
+
+
+ );
+}
+
+export default withPolarisExample(IndexTableWithPaginationExample);
diff --git a/polaris.shopify.com/pages/examples/resource-list-with-pagination.tsx b/polaris.shopify.com/pages/examples/resource-list-with-pagination.tsx
new file mode 100644
index 00000000000..99e8700ee1e
--- /dev/null
+++ b/polaris.shopify.com/pages/examples/resource-list-with-pagination.tsx
@@ -0,0 +1,57 @@
+import {
+ LegacyCard,
+ ResourceList,
+ Avatar,
+ ResourceItem,
+ Text,
+} from '@shopify/polaris';
+import React from 'react';
+import {withPolarisExample} from '../../src/components/PolarisExampleWrapper';
+
+function ResourceListWithPaginationExample() {
+ return (
+
+ {},
+ }}
+ renderItem={(item) => {
+ const {id, url, name, location} = item;
+ const media = ;
+
+ return (
+
+
+ {name}
+
+ {location}
+
+ );
+ }}
+ />
+
+ );
+}
+
+export default withPolarisExample(ResourceListWithPaginationExample);