Skip to content

Commit

Permalink
Add support for headings with padding right
Browse files Browse the repository at this point in the history
  • Loading branch information
Teri Leung committed Dec 12, 2023
1 parent e530a44 commit fe4b698
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 21 deletions.
6 changes: 6 additions & 0 deletions .changeset/poor-students-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/polaris': minor
---

1. Added IndexTable heading props to support additional right padding
2. Fixed an issue where headings with sort and tooltip don't align right properly.
10 changes: 9 additions & 1 deletion polaris-react/src/components/IndexTable/IndexTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -837,11 +837,19 @@ $loading-panel-height: 53px;
.TableHeading-align-end {
text-align: right;

[class*='TooltipContainer'] {
[class*='TooltipContainer'],
[class*='SortableTableHeadingWithCustomMarkup'] {
justify-content: end;
}
}

.TableHeading-extra-padding-right {
// stylelint-disable -- polaris component custom properties
--pc-index-table-heading-extra-padding-right: 0rem;
padding-right: var(--pc-index-table-heading-extra-padding-right);
// stylelint-enable
}

.TableHeading-sortable {
background: var(--p-color-bg-surface-secondary);
}
Expand Down
195 changes: 195 additions & 0 deletions polaris-react/src/components/IndexTable/IndexTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2993,6 +2993,201 @@ export function WithSortableCustomHeadings() {
);
}

export function WithHeadingWithPaddingEnd() {
const [sortIndex, setSortIndex] = useState(0);
const [sortDirection, setSortDirection] =
useState<IndexTableProps['sortDirection']>('descending');

const sortToggleLabels = {
0: {ascending: 'A-Z', descending: 'Z-A'},
1: {ascending: 'Ascending', descending: 'Descending'},
2: {ascending: 'Newest', descending: 'Oldest'},
3: {ascending: 'Ascending', descending: 'Ascending'},
4: {ascending: 'A-Z', descending: 'Z-A'},
5: {ascending: 'A-Z', descending: 'Z-A'},
6: {ascending: 'A-Z', descending: 'Z-A'},
7: {ascending: 'A-Z', descending: 'Z-A'},
};

const initialRows = [
{
id: '3411',
url: '#',
name: 'Mae Jemison',
date: '2022-02-04',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
fulfillmentStatus: 'Fulfilled',
paymentStatus: 'Paid',
notes: '',
},
{
id: '2561',
url: '#',
date: '2022-01-19',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
fulfillmentStatus: 'Fulfilled',
paymentStatus: 'Not paid',
notes: 'This customer lives on the 3rd floor',
},
{
id: '1245',
url: '#',
date: '2021-12-12',
name: 'Anne-Marie Johnson',
location: 'Portland, USA',
orders: 10,
amountSpent: '$250',
fulfillmentStatus: 'Fulfilled',
paymentStatus: 'Not paid',
notes: '',
},
{
id: '8741',
url: '#',
date: '2022-05-11',
name: 'Bradley Stevens',
location: 'Hialeah, USA',
orders: 5,
amountSpent: '$26',
fulfillmentStatus: 'Unfulfilled',
paymentStatus: 'Not paid',
notes: 'This customer has requested fragile delivery',
},
];
const [sortedRows, setSortedRows] = useState(
sortRows(initialRows, sortIndex, sortDirection),
);

const resourceName = {
singular: 'customer',
plural: 'customers',
};

const rows = sortedRows ?? initialRows;

const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(rows);

function handleClickSortHeading(index, direction) {
setSortIndex(index);
setSortDirection(direction);
const newSortedRows = sortRows(rows, index, direction);
setSortedRows(newSortedRows);
}

function sortRows(localRows, index, direction) {
return [...localRows].sort((rowA, rowB) => {
const key = index === 0 ? 'name' : 'location';
if (rowA[key] < rowB[key]) {
return direction === 'descending' ? -1 : 1;
}
if (rowA[key] > rowB[key]) {
return direction === 'descending' ? 1 : -1;
}
return 0;
});
}

const rowMarkup = rows.map(
(
{
id,
name,
date,
location,
orders,
amountSpent,
fulfillmentStatus,
paymentStatus,
notes,
},
index,
) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<Text fontWeight="bold" as="span">
{name}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>{date}</IndexTable.Cell>
<IndexTable.Cell>
<Text as="span" alignment="end" numeric>
{orders}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>
<Box paddingInlineEnd="600">
<Text as="span" alignment="end" numeric>
{amountSpent}
</Text>
</Box>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{fulfillmentStatus}</IndexTable.Cell>
<IndexTable.Cell>{paymentStatus}</IndexTable.Cell>
<IndexTable.Cell>{notes}</IndexTable.Cell>
</IndexTable.Row>
),
);

return (
<LegacyCard>
<IndexTable
condensed={useBreakpoints().smDown}
resourceName={resourceName}
itemCount={rows.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{
title: 'Name',
tooltipContent: 'I am a wide tooltip describing the Name column',
tooltipWidth: 'wide',
},
{title: 'Date', tooltipContent: 'I am the Date tooltip'},
{
alignment: 'end',
id: 'order-count',
title: 'Order count',
},
{
alignment: 'end',
title: 'Amount spent',
tooltipContent:
'I am a wide Amount spent tooltip that stays when clicked',
tooltipWidth: 'wide',
paddingEnd: '600',
},
{title: 'Location'},
{title: 'Fulfillment status'},
{title: 'Payment status'},
{title: 'Notes'},
]}
sortable={[true, true, false, true, true, false, false]}
sortDirection={sortDirection}
sortColumnIndex={sortIndex}
onSort={handleClickSortHeading}
sortToggleLabels={sortToggleLabels}
lastColumnSticky
>
{rowMarkup}
</IndexTable>
</LegacyCard>
);
}

export function WithCustomTooltips() {
const customers = [
{
Expand Down
74 changes: 56 additions & 18 deletions polaris-react/src/components/IndexTable/IndexTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {useRef, useState, useEffect, useCallback, useMemo} from 'react';
import {SortAscendingMajor, SortDescendingMajor} from '@shopify/polaris-icons';
import {CSSTransition} from 'react-transition-group';
import {themeDefault, toPx} from '@shopify/polaris-tokens';
import type {SpaceScale} from '@shopify/polaris-tokens';

import {debounce} from '../../utilities/debounce';
import {useToggle} from '../../utilities/use-toggle';
Expand Down Expand Up @@ -65,6 +66,8 @@ interface IndexTableHeadingBase {
* When not specified, the value from IndexTable.defaultSortDirection will be used.
*/
defaultSortDirection?: IndexTableSortDirection;
/** Horizontal end spacing around title. Accepts a spacing token. */
paddingEnd?: SpaceScale;
}

interface IndexTableHeadingTitleString extends IndexTableHeadingBase {
Expand Down Expand Up @@ -933,6 +936,12 @@ function IndexTableBase({
headingContent = heading.title;
}

const style = {
'--pc-index-table-heading-extra-padding-right': heading.paddingEnd
? `var(--p-space-${heading.paddingEnd})`
: '0',
} as React.CSSProperties;

if (sortable?.[index]) {
const isCurrentlySorted = index === sortColumnIndex;
const isPreviouslySorted =
Expand Down Expand Up @@ -1025,20 +1034,33 @@ function IndexTableBase({
if (!heading.tooltipContent) {
return (
// Regular header with sort icon and sort direction tooltip
<Tooltip
{...defaultTooltipProps}
content={sortTooltipContent}
preferredPosition="above"
<div
style={style}
className={classNames(
heading.paddingEnd && styles['TableHeading-extra-padding-right'],
)}
>
{sortMarkup}
</Tooltip>
<Tooltip
{...defaultTooltipProps}
content={sortTooltipContent}
preferredPosition="above"
>
{sortMarkup}
</Tooltip>
</div>
);
}

if (heading.tooltipContent) {
return (
// Header text and sort icon have separate tooltips
<div className={styles.SortableTableHeadingWithCustomMarkup}>
<div
className={classNames(
styles.SortableTableHeadingWithCustomMarkup,
heading.paddingEnd && styles['TableHeading-extra-padding-right'],
)}
style={style}
>
<UnstyledButton {...defaultSortButtonProps}>
<Tooltip {...defaultHeaderTooltipProps}>
<span className={styles.TableHeadingUnderline}>
Expand All @@ -1062,20 +1084,36 @@ function IndexTableBase({
if (heading.tooltipContent) {
return (
// Non-sortable header with tooltip
<Tooltip {...defaultHeaderTooltipProps} activatorWrapper="span">
<span
className={classNames(
styles.TableHeadingUnderline,
styles.SortableTableHeaderWrapper,
)}
>
{headingContent}
</span>
</Tooltip>
<div
style={style}
className={classNames(
heading.paddingEnd && styles['TableHeading-extra-padding-right'],
)}
>
<Tooltip {...defaultHeaderTooltipProps} activatorWrapper="span">
<span
className={classNames(
styles.TableHeadingUnderline,
styles.SortableTableHeaderWrapper,
)}
>
{headingContent}
</span>
</Tooltip>
</div>
);
}

return headingContent;
return (
<div
style={style}
className={classNames(
heading.paddingEnd && styles['TableHeading-extra-padding-right'],
)}
>
{headingContent}
</div>
);
}

function handleSelectPage(checked: boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,11 @@ describe('<IndexTable>', () => {
expect(index).toContainReactComponent('table', {
className: 'Table Table-sticky Table-sticky-last',
});
expect(index).toContainReactComponent('th', {
children: title,
const lastHeading = index.find('th', {
className: 'TableHeading TableHeading-last',
});
expect(lastHeading).not.toBeNull();
expect(lastHeading).toContainReactText(title);
});

it('does not render a sticky last heading if `lastColumnSticky` prop is true and last heading is hidden', () => {
Expand Down

0 comments on commit fe4b698

Please sign in to comment.