UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

1,525 lines (1,362 loc) 40.5 kB
--- name: Index table category: Lists and tables keywords: - ResourceList - index - table - list - collections - items - objects - list of products - list of orders - product lists - order lists - collections lists - collection lists - list of collections - product listings list - channel lists - resource list attributes - list attributes - exceptions list - list secondary actions - secondary actions in a list - list of resources - filter - sort --- # Index table An index table displays a collection of objects of the same type, like orders or products. The main job of an index table is to help merchants get an at-a-glance of the objects to perform actions or navigate to a full-page representation of it. Index tables can also: - Support [customized index rows and columns](https://polaris.shopify.com/components/lists-and-tables/resource-item) - Include bulk actions so merchants can act on multiple objects at once - Support sorting and [filtering](https://polaris.shopify.com/components/lists-and-tables/filters) of long lists - Be paired with pagination to make long lists digestible --- ## Examples ### Simple index table A index table with simple items and no bulk actions, sorting, or filtering. ```jsx function SimpleIndexTableExample() { const customers = [ { id: '3411', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2561', url: 'customers/256', 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) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### Simple small screen index table A small screen index table with simple items and no bulk actions, sorting, or filtering. ```jsx function SimpleSmallScreenIndexTableExample() { const customers = [ { id: '3412', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2562', url: 'customers/256', 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) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <div style={{padding: '12px 16px'}}> <p> <TextStyle variation="strong">{name}</TextStyle> </p> <p>{location}</p> <p>{orders}</p> <p>{amountSpent}</p> </div> </IndexTable.Row> ), ); return ( <div style={{width: '430px'}}> <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} condensed headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> </div> ); } ``` ### IndexTable with empty state Use to explain the purpose of a index table when no resources exist yet. This allows a smooth transition from a list in a loading state to a list where zero, one, or many resources exist. ```jsx function IndexTableWithCustomEmptyStateExample() { const customers = []; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const emptyStateMarkup = ( <EmptySearchResult title={'No customers yet'} description={'Try changing the filters or search term'} withIllustration /> ); const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} emptyState={emptyStateMarkup} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with bulk actions Allows merchants to select items and perform an action on the selection. ```jsx function IndexTableWithBulkActionsExample() { const customers = [ { id: '3413', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2563', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const promotedBulkActions = [ { content: 'Edit customers', onAction: () => console.log('Todo: implement bulk edit'), }, ]; const bulkActions = [ { content: 'Add tags', onAction: () => console.log('Todo: implement bulk add tags'), }, { content: 'Remove tags', onAction: () => console.log('Todo: implement bulk remove tags'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement bulk delete'), }, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} bulkActions={bulkActions} promotedBulkActions={promotedBulkActions} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with multiple promoted bulk actions Allows merchants to select items and perform different actions on the selection. ```jsx function IndexTableWithMultiplePromotedBulkActionsExample() { const customers = [ { id: '3413', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2563', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const promotedBulkActions = [ { content: 'Capture payments', onAction: () => console.log('Todo: implement payment capture'), }, { title: 'Edit customers', actions: [ { content: 'Add customers', onAction: () => console.log('Todo: implement adding customers'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement deleting customers'), }, ], }, { title: 'Export', actions: [ { content: 'Export as PDF', onAction: () => console.log('Todo: implement PDF exporting'), }, { content: 'Export as CSV', onAction: () => console.log('Todo: implement CSV exporting'), }, ], }, ]; const bulkActions = [ { content: 'Add tags', onAction: () => console.log('Todo: implement bulk add tags'), }, { content: 'Remove tags', onAction: () => console.log('Todo: implement bulk remove tags'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement bulk delete'), }, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} bulkActions={bulkActions} promotedBulkActions={promotedBulkActions} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with bulk actions and selection across pages Allows merchants to select items, perform an action on the selection and select resources across pages. ```jsx function IndexTableWithBulkActionsAndSelectionAcrossPagesExample() { const customers = [ { id: '3414', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2564', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const promotedBulkActions = [ { content: 'Edit customers', onAction: () => console.log('Todo: implement bulk edit'), }, ]; const bulkActions = [ { content: 'Add tags', onAction: () => console.log('Todo: implement bulk add tags'), }, { content: 'Remove tags', onAction: () => console.log('Todo: implement bulk remove tags'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement bulk delete'), }, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} hasMoreItems bulkActions={bulkActions} promotedBulkActions={promotedBulkActions} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with loading state Notifies merchants that index table items are being processed. ```jsx function IndexTableWithLoadingExample() { const customers = [ { id: '3415', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2565', url: 'customers/256', 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) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} loading headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with filtering Allows merchants to narrow the index table to a subset of the original items. ```jsx function IndexTableWithFilteringExample() { const customers = [ { id: '3416', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2566', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const [taggedWith, setTaggedWith] = useState('VIP'); const [queryValue, setQueryValue] = useState(null); const [sortValue, setSortValue] = useState('today'); const handleTaggedWithChange = useCallback( (value) => setTaggedWith(value), [], ); const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []); const handleQueryValueRemove = useCallback(() => setQueryValue(null), []); const handleClearAll = useCallback(() => { handleTaggedWithRemove(); handleQueryValueRemove(); }, [handleQueryValueRemove, handleTaggedWithRemove]); const handleSortChange = useCallback((value) => setSortValue(value), []); const filters = [ { key: 'taggedWith', label: 'Tagged with', filter: ( <TextField label="Tagged with" value={taggedWith} onChange={handleTaggedWithChange} autoComplete="off" labelHidden /> ), shortcut: true, }, ]; const appliedFilters = !isEmpty(taggedWith) ? [ { key: 'taggedWith', label: disambiguateLabel('taggedWith', taggedWith), onRemove: handleTaggedWithRemove, }, ] : []; const sortOptions = [ {label: 'Today', value: 'today'}, {label: 'Yesterday', value: 'yesterday'}, {label: 'Last 7 days', value: 'lastWeek'}, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <div style={{padding: '16px', display: 'flex'}}> <div style={{flex: 1}}> <Filters queryValue={queryValue} filters={filters} appliedFilters={appliedFilters} onQueryChange={setQueryValue} onQueryClear={handleQueryValueRemove} onClearAll={handleClearAll} /> </div> <div style={{paddingLeft: '0.25rem'}}> <Select labelInline label="Sort by" options={sortOptions} value={sortValue} onChange={handleSortChange} /> </div> </div> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); function disambiguateLabel(key, value) { switch (key) { case 'taggedWith': return `Tagged with ${value}`; default: return value; } } function isEmpty(value) { if (Array.isArray(value)) { return value.length === 0; } else { return value === '' || value == null; } } } ``` ### Index table with row status An index table with rows differentiated by status. ```jsx function IndexTableWithRowStatusExample() { const customers = [ { id: '3411', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', status: 'success', }, { id: '2561', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', status: 'subdued', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const rowMarkup = customers.map( ({id, name, location, orders, amountSpent, status}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} status={status} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> ); } ``` ### Index table with sticky last column An index table with a sticky last column that stays visible on scroll. The last heading will also be sticky if not hidden. ```jsx function StickyLastCellIndexTableExample() { const customers = [ { id: '3411', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2561', url: 'customers/256', 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) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent', hidden: false}, ]} lastColumnSticky > {rowMarkup} </IndexTable> </Card> ); } ``` ### Index table without checkboxes An index table without checkboxes and bulk actions. ```jsx function IndexTableWithoutCheckboxesExample() { const customers = [ { id: '3411', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2561', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} position={index}> <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <IndexTable resourceName={resourceName} itemCount={customers.length} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent', hidden: false}, ]} selectable={false} > {rowMarkup} </IndexTable> </Card> ); } ``` ### IndexTable with all of its elements Use as a broad example that includes most of the elements and props available to index table. ```jsx function IndexTableWithAllElementsExample() { const customers = [ { id: '3417', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2567', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const [taggedWith, setTaggedWith] = useState('VIP'); const [queryValue, setQueryValue] = useState(null); const [sortValue, setSortValue] = useState('today'); const handleTaggedWithChange = useCallback( (value) => setTaggedWith(value), [], ); const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []); const handleQueryValueRemove = useCallback(() => setQueryValue(null), []); const handleClearAll = useCallback(() => { handleTaggedWithRemove(); handleQueryValueRemove(); }, [handleQueryValueRemove, handleTaggedWithRemove]); const handleSortChange = useCallback((value) => setSortValue(value), []); const promotedBulkActions = [ { content: 'Edit customers', onAction: () => console.log('Todo: implement bulk edit'), }, ]; const bulkActions = [ { content: 'Add tags', onAction: () => console.log('Todo: implement bulk add tags'), }, { content: 'Remove tags', onAction: () => console.log('Todo: implement bulk remove tags'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement bulk delete'), }, ]; const filters = [ { key: 'taggedWith', label: 'Tagged with', filter: ( <TextField label="Tagged with" value={taggedWith} onChange={handleTaggedWithChange} autoComplete="off" labelHidden /> ), shortcut: true, }, ]; const appliedFilters = !isEmpty(taggedWith) ? [ { key: 'taggedWith', label: disambiguateLabel('taggedWith', taggedWith), onRemove: handleTaggedWithRemove, }, ] : []; const sortOptions = [ {label: 'Today', value: 'today'}, {label: 'Yesterday', value: 'yesterday'}, {label: 'Last 7 days', value: 'lastWeek'}, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <IndexTable.Cell> <TextStyle variation="strong">{name}</TextStyle> </IndexTable.Cell> <IndexTable.Cell>{location}</IndexTable.Cell> <IndexTable.Cell>{orders}</IndexTable.Cell> <IndexTable.Cell>{amountSpent}</IndexTable.Cell> </IndexTable.Row> ), ); return ( <Card> <div style={{padding: '16px', display: 'flex'}}> <div style={{flex: 1}}> <Filters queryValue={queryValue} filters={filters} appliedFilters={appliedFilters} onQueryChange={setQueryValue} onQueryClear={handleQueryValueRemove} onClearAll={handleClearAll} /> </div> <div style={{paddingLeft: '0.25rem'}}> <Select labelInline label="Sort by" options={sortOptions} value={sortValue} onChange={handleSortChange} /> </div> </div> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} hasMoreItems bulkActions={bulkActions} promotedBulkActions={promotedBulkActions} lastColumnSticky headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent', hidden: false}, ]} > {rowMarkup} </IndexTable> </Card> ); function disambiguateLabel(key, value) { switch (key) { case 'taggedWith': return `Tagged with ${value}`; default: return value; } } function isEmpty(value) { if (Array.isArray(value)) { return value.length === 0; } else { return value === '' || value == null; } } } ``` ### Small screen IndexTable with all of its elements Use as a broad example that includes most of the elements and props available to index table. ```jsx function SmallScreenIndexTableWithAllElementsExample() { const customers = [ { id: '3418', url: 'customers/341', name: 'Mae Jemison', location: 'Decatur, USA', orders: 20, amountSpent: '$2,400', }, { id: '2568', url: 'customers/256', name: 'Ellen Ochoa', location: 'Los Angeles, USA', orders: 30, amountSpent: '$140', }, ]; const resourceName = { singular: 'customer', plural: 'customers', }; const {selectedResources, allResourcesSelected, handleSelectionChange} = useIndexResourceState(customers); const [taggedWith, setTaggedWith] = useState('VIP'); const [queryValue, setQueryValue] = useState(null); const [sortValue, setSortValue] = useState('today'); const handleTaggedWithChange = useCallback( (value) => setTaggedWith(value), [], ); const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []); const handleQueryValueRemove = useCallback(() => setQueryValue(null), []); const handleClearAll = useCallback(() => { handleTaggedWithRemove(); handleQueryValueRemove(); }, [handleQueryValueRemove, handleTaggedWithRemove]); const handleSortChange = useCallback((value) => setSortValue(value), []); const promotedBulkActions = [ { content: 'Edit customers', onAction: () => console.log('Todo: implement bulk edit'), }, ]; const bulkActions = [ { content: 'Add tags', onAction: () => console.log('Todo: implement bulk add tags'), }, { content: 'Remove tags', onAction: () => console.log('Todo: implement bulk remove tags'), }, { content: 'Delete customers', onAction: () => console.log('Todo: implement bulk delete'), }, ]; const filters = [ { key: 'taggedWith', label: 'Tagged with', filter: ( <TextField label="Tagged with" value={taggedWith} onChange={handleTaggedWithChange} autoComplete="off" labelHidden /> ), shortcut: true, }, ]; const appliedFilters = !isEmpty(taggedWith) ? [ { key: 'taggedWith', label: disambiguateLabel('taggedWith', taggedWith), onRemove: handleTaggedWithRemove, }, ] : []; const sortOptions = [ {label: 'Today', value: 'today'}, {label: 'Yesterday', value: 'yesterday'}, {label: 'Last 7 days', value: 'lastWeek'}, ]; const rowMarkup = customers.map( ({id, name, location, orders, amountSpent}, index) => ( <IndexTable.Row id={id} key={id} selected={selectedResources.includes(id)} position={index} > <div style={{padding: '.75rem 1rem'}}> <p> <TextStyle variation="strong">{name}</TextStyle> </p> <p>{location}</p> <p>{orders}</p> <p>{amountSpent}</p> </div> </IndexTable.Row> ), ); return ( <div style={{width: '430px'}}> <Card> <div style={{padding: '16px', display: 'flex'}}> <div style={{flex: 1}}> <Filters queryValue={queryValue} filters={filters} appliedFilters={appliedFilters} onQueryChange={setQueryValue} onQueryClear={handleQueryValueRemove} onClearAll={handleClearAll} /> </div> <div style={{paddingLeft: '0.25rem'}}> <Select labelInline label="Sort by" options={sortOptions} value={sortValue} onChange={handleSortChange} /> </div> </div> <IndexTable resourceName={resourceName} itemCount={customers.length} selectedItemsCount={ allResourcesSelected ? 'All' : selectedResources.length } onSelectionChange={handleSelectionChange} hasMoreItems condensed bulkActions={bulkActions} promotedBulkActions={promotedBulkActions} headings={[ {title: 'Name'}, {title: 'Location'}, {title: 'Order count'}, {title: 'Amount spent'}, ]} > {rowMarkup} </IndexTable> </Card> </div> ); function disambiguateLabel(key, value) { switch (key) { case 'taggedWith': return `Tagged with ${value}`; default: return value; } } function isEmpty(value) { if (Array.isArray(value)) { return value.length === 0; } else { return value === '' || value == null; } } } ``` --- ## Build Using an index table in a project involves combining the following components and subcomponents: - IndexTable - [IndexTableRow](#index-table-row) - [IndexTableCell](#index-table-cell) - [Filters](https://polaris.shopify.com/components/lists-and-tables/filters) (optional) - Pagination component (optional) <!-- hint --> The index table component provides the UI elements for list sorting, filtering, and pagination, but doesn’t provide the logic for these operations. When a sort option is changed, filter added, or second page requested, you’ll need to handle that event (including any network requests) and then update the component with new props. <!-- end --> --- ## Purpose Shopify is organized around objects that represent merchants businesses, like customers, products, and orders. Each individual order, for example, is given a dedicated page that can be linked to. In Shopify, we call these types of objects _resources_, and we call the object’s dedicated page its _details page_. ### Problem Take orders as an example. Merchants may have a lot of them. They need a way to scan their orders, view the different attributes on each order, and find out which ones need action first. In other words, they need a way find an individual order, call up more information about it, and take action on it. ### Solution Index tables function as: - A content format, presenting a set of individual resources with multiple columns of information for each - A system for taking action on one or more individual resources - A way to navigate to an individual resource’s details page Because a details page displays all the content and actions for an individual resource, you can think of a resource list as a summary of these details pages. In this way resource lists bridge a middle level in Shopify’s navigation hierarchy. --- ## Best practices Index tables should: - Have items that perform an action when clicked. The action should navigate to the resource’s details page or otherwise provide more detail about the item. - [Customize the content and layout](https://polaris.shopify.com/components/lists-and-tables/resource-item) of their items rows to surface information to support merchants’ needs. - Support sorting if the list can be long, and especially if different merchant tasks benefit from different sort orders. - Support [filtering](https://polaris.shopify.com/components/lists-and-tables/filters) if the list can be long. - Paginate when the current list contains more than 50 items. - Use the [skeleton page](https://polaris.shopify.com/components/feedback-indicators/skeleton-page) component on initial page load for the rest of the page if the loading prop is true and items are processing. Index tables can optionally: - Provide bulk actions for tasks that are often applied to many list items at once. For example, merchants may want to add the same tag to a large number of products. --- ## Content guidelines Index tables should: - Identify the type of resource, usually with a heading <!-- usagelist --> #### Do - Products - Showing 50 products #### Don’t - _No heading_ <!-- end --> - Indicate when not all members of a resource are being shown. For a card summarizing and linking to recently purchased products: <!-- usagelist --> #### Do - Popular products this week #### Don’t - Products <!-- end --> - Follow the verb + noun formula for bulk actions - Follow the [content guidelines for filter options and applied filters](https://polaris.shopify.com/components/lists-and-tables/filters#section-content-guidelines) --- <a name="index-table-row"></a> ## IndexTableRow An `IndexTableRow` is used to render a row representing an item within an `IndexTable` ### IndexTableRow properties | Prop | Type | Description | | -------- | --------- | --------------------------------------------------------------- | | id | string | A unique identifier for the row | | selected | boolean | A boolean property indicating whether the row is selected | | position | number | The index position of the row | | subdued | boolean | A boolean property indicating whether the row should be subdued | | status | RowStatus | A property indicating whether the row should have a status | <a name="index-table-cell"></a> ## IndexTableCell An `IndexTableCell` is used to render a single cell within an `IndexTableRow` ### IndexTableCell properties | Prop | Type | Description | | ----- | ------- | -------------------------------------------------------------------------------- | | flush | boolean | A boolean property indicating whether the cell should remove the default padding | --- ## Related components - To create an actionable list of related items that link to details pages, such as a list of customers, use the [resource list component](https://polaris.shopify.com/components/lists-and-tables/resource-list) - To present structured data for comparison and analysis, like when helping merchants to gain insights or review analytics, use the [data table component](https://polaris.shopify.com/components/lists-and-tables/data-table) - To display a simple list of related content, [use the list component](https://polaris.shopify.com/components/lists-and-tables/list)