Skip to content

Commit

Permalink
[IndexTable.Row] Add ability to hide selectable checkbox on a per row…
Browse files Browse the repository at this point in the history
… basis (#11944)

<!--
  ☝️How to write a good PR title:
- Prefix it with [ComponentName] (if applicable), for example: [Button]
  - Start with a verb, for example: Add, Delete, Improve, Fix…
  - Give as much context as necessary and as little as possible
  - Open it as a draft if it’s a work in progress
-->

### WHY are these changes introduced?

UX desires the ability to control if specific table rows shouldn't show
a checkbox. This is particularly useful when you have an IndexTable that
renders nested resources, but only has bulk actions that are applicable
to the parent resource.

### WHAT is this pull request doing?

Here's an example scenario where you might want to hide the selectable
checkbox rather than just disabling it.

Before:
<img width="725" alt="Index table with subheaders that have disabled
checkboxes"
src="https://github.com/Shopify/polaris/assets/510085/11a68680-a972-4d90-b7ad-04167bdf0933">

After:
<img width="725" alt="Index table with subheaders that dont show
checkboxes at all"
src="https://github.com/Shopify/polaris/assets/510085/86442af8-a401-4438-9ee3-a69da17a4514">


### How to 🎩

🖥 [Local development
instructions](https://github.com/Shopify/polaris/blob/main/README.md#install-dependencies-and-build-workspaces)
🗒 [General tophatting
guidelines](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md)
📄 [Changelog
guidelines](https://github.com/Shopify/polaris/blob/main/.github/CONTRIBUTING.md#changelog)

[CodeSandbox
Example](https://codesandbox.io/p/sandbox/agitated-glade-3wqn49)

Steps: 
- Visit [this
example](https://codesandbox.io/p/sandbox/agitated-glade-3wqn49) where
hideSelectable is set to true for the subheader rows
  - Verify that no checkboxes are shown for the subheader rows
  - Verify that columns still line up as expected
- Visit [this example](https://3r9wzf.csb.app/) where hideSelectable is
set to false
- Verify that disabled checkboxes are still shown for for the subheader
rows
- Visit [this example](https://pry4q2.csb.app/) where hideSelectable is
true and selectable is false on IndexTable
  - Verify that columns still line up as expected 

### 🎩 checklist

- [x] Tested a
[snapshot](https://github.com/Shopify/polaris/blob/main/documentation/Releasing.md#-snapshot-releases)
- [ ] Tested on
[mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing)
- [x] Tested on [multiple
browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers)
- [x] 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
  • Loading branch information
stefanlegg committed May 2, 2024
1 parent 45da657 commit d1d69e9
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-masks-walk.md
@@ -0,0 +1,5 @@
---
'@shopify/polaris': patch
---

Add support for hiding selectable checkbox on a per `IndexTable.Row` basis via `hideSelectable` prop`
25 changes: 16 additions & 9 deletions polaris-react/src/components/IndexTable/components/Row/Row.tsx
Expand Up @@ -6,6 +6,7 @@ import {
SelectionType,
useIndexSelectionChange,
} from '../../../../utilities/index-provider';
import {Cell} from '../Cell';
import {Checkbox} from '../Checkbox';
import {classNames, variationName} from '../../../../utilities/css';
import {RowContext, RowHoveredContext} from '../../../../utilities/index-table';
Expand All @@ -21,6 +22,8 @@ export interface RowProps {
children: React.ReactNode;
/** A unique identifier for the row */
id: string;
/** Whether the row should hide the selectable checkbox while the parent IndexTable is selectable */
hideSelectable?: boolean;
/** Whether the row is selected */
selected?: boolean | 'indeterminate';
/** The zero-indexed position of the row. Used for Shift key multi-selection */
Expand All @@ -47,6 +50,7 @@ export interface RowProps {

export const Row = memo(function Row({
children,
hideSelectable,
selected,
id,
position,
Expand All @@ -58,7 +62,8 @@ export const Row = memo(function Row({
onNavigation,
onClick,
}: RowProps) {
const {selectable, selectMode, condensed} = useIndexRow();
const {selectable: tableIsSelectable, selectMode, condensed} = useIndexRow();
const rowIsSelectable = tableIsSelectable && !hideSelectable;
const onSelectionChange = useIndexSelectionChange();
const {
value: hovered,
Expand All @@ -73,7 +78,7 @@ export const Row = memo(function Row({

if (
disabled ||
!selectable ||
!rowIsSelectable ||
('key' in event && event.key !== ' ') ||
!onSelectionChange
)
Expand All @@ -95,7 +100,7 @@ export const Row = memo(function Row({
selectionRange,
position,
disabled,
selectable,
rowIsSelectable,
],
);

Expand Down Expand Up @@ -128,20 +133,20 @@ export const Row = memo(function Row({
styles.TableRow,
rowType === 'subheader' && styles['TableRow-subheader'],
rowType === 'child' && styles['TableRow-child'],
selectable && condensed && styles.condensedRow,
rowIsSelectable && condensed && styles.condensedRow,
selected && styles['TableRow-selected'],
hovered && !condensed && styles['TableRow-hovered'],
disabled && styles['TableRow-disabled'],
tone && styles[variationName('tone', tone)],
!selectable &&
!rowIsSelectable &&
!onClick &&
!primaryLinkElement.current &&
styles['TableRow-unclickable'],
);

let handleRowClick;

if ((!disabled && selectable) || onClick || primaryLinkElement.current) {
if ((!disabled && rowIsSelectable) || onClick || primaryLinkElement.current) {
handleRowClick = (event: React.MouseEvent) => {
if (rowType === 'subheader') return;

Expand Down Expand Up @@ -184,9 +189,11 @@ export const Row = memo(function Row({
}

const RowWrapper = condensed ? 'li' : 'tr';
const checkboxMarkup = selectable ? (
const checkboxMarkup = hideSelectable ? (
<Cell />
) : (
<Checkbox accessibilityLabel={accessibilityLabel} />
) : null;
);

return (
<RowContext.Provider value={contextValue}>
Expand All @@ -200,7 +207,7 @@ export const Row = memo(function Row({
onClick={handleRowClick}
ref={tableRowCallbackRef}
>
{checkboxMarkup}
{tableIsSelectable ? checkboxMarkup : null}
{children}
</RowWrapper>
</RowHoveredContext.Provider>
Expand Down
Expand Up @@ -14,6 +14,7 @@ import {Link} from '../../../../Link';
import {Checkbox as PolarisCheckbox} from '../../../../Checkbox';
import styles from '../../../IndexTable.module.css';
import type {Range} from '../../../../../utilities/index-provider';
import {Cell} from '../../Cell';

const defaultEvent = {
preventDefault: noop,
Expand Down Expand Up @@ -78,6 +79,29 @@ describe('<Row />', () => {
expect(row).not.toContainReactComponent(Checkbox);
});

it('renders checkboxes when hideSelectable is false and selectable set to true in IndexTable', () => {
const row = mountWithTable(
<Row {...defaultProps} hideSelectable={false}>
<th>Child</th>
</Row>,
{indexTableProps: {selectable: true}},
);

expect(row).toContainReactComponent(Checkbox);
});

it('does not render checkboxes when hideSelectable is true and selectable set to true in IndexTable', () => {
const row = mountWithTable(
<Row {...defaultProps} hideSelectable>
<th>Child</th>
</Row>,
{indexTableProps: {selectable: true}},
);

expect(row).not.toContainReactComponent(Checkbox);
expect(row).toContainReactComponent(Cell, {});
});

it('renders a RowHoveredContext provider', () => {
const row = mountWithTable(
<Row id="id" selected position={1}>
Expand Down

0 comments on commit d1d69e9

Please sign in to comment.