Skip to content

Commit acc3395

Browse files
priyajeetmergify[bot]
authored andcommitted
feat(content-picker): use radios for single select (#1685)
* feat(content-picker): use radios for single select * feat(content-picker): add selectionCell renderer * feat(content-picker): remove maxSelectable from readme * feat(content-picker): PR Feedback * feat(content-picker): revert flow type change * feat(content-picker): PR Feedback * feat(content-picker): PR Feedback
1 parent 003fbfc commit acc3395

File tree

8 files changed

+108
-32
lines changed

8 files changed

+108
-32
lines changed

src/elements/content-picker/Content.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Props = {
1818
extensionsWhitelist: string[],
1919
focusedRow: number,
2020
hasHitSelectionLimit: boolean,
21+
isSingleSelect: boolean,
2122
isSmall: boolean,
2223
onFocusChange: Function,
2324
onItemClick: Function,
@@ -53,6 +54,7 @@ const Content = ({
5354
currentCollection,
5455
tableRef,
5556
canSetShareAccess,
57+
isSingleSelect,
5658
onItemClick,
5759
onItemSelect,
5860
onShareAccessChange,
@@ -76,6 +78,7 @@ const Content = ({
7678
tableRef={tableRef}
7779
canSetShareAccess={canSetShareAccess}
7880
hasHitSelectionLimit={hasHitSelectionLimit}
81+
isSingleSelect={isSingleSelect}
7982
selectableType={selectableType}
8083
onItemSelect={onItemSelect}
8184
onItemClick={onItemClick}

src/elements/content-picker/ContentPicker.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,8 @@ class ContentPicker extends Component<Props, State> {
11531153
const { id, offset, permissions, totalCount }: Collection = currentCollection;
11541154
const { can_upload }: BoxItemPermission = permissions || {};
11551155
const selectedCount: number = Object.keys(selected).length;
1156-
const hasHitSelectionLimit: boolean = selectedCount === maxSelectable && maxSelectable !== 1;
1156+
const isSingleSelect = maxSelectable === 1;
1157+
const hasHitSelectionLimit: boolean = selectedCount === maxSelectable && !isSingleSelect;
11571158
const allowUpload: boolean = canUpload && !!can_upload;
11581159
const allowCreate: boolean = canCreateNewFolder && !!can_upload;
11591160
const styleClassName = classNames('be bcp', className);
@@ -1195,6 +1196,7 @@ class ContentPicker extends Component<Props, State> {
11951196
extensionsWhitelist={extensions}
11961197
hasHitSelectionLimit={hasHitSelectionLimit}
11971198
currentCollection={currentCollection}
1199+
isSingleSelect={isSingleSelect}
11981200
tableRef={this.tableRef}
11991201
onItemSelect={this.select}
12001202
onItemClick={this.onItemClick}
@@ -1204,6 +1206,7 @@ class ContentPicker extends Component<Props, State> {
12041206
<Footer
12051207
selectedCount={selectedCount}
12061208
hasHitSelectionLimit={hasHitSelectionLimit}
1209+
isSingleSelect={isSingleSelect}
12071210
onSelectedClick={this.showSelected}
12081211
onChoose={this.choose}
12091212
onCancel={this.cancel}

src/elements/content-picker/Footer.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Props = {
2020
children?: any,
2121
chooseButtonLabel?: string,
2222
hasHitSelectionLimit: boolean,
23+
isSingleSelect: boolean,
2324
onCancel: Function,
2425
onChoose: Function,
2526
onSelectedClick: Function,
@@ -30,6 +31,7 @@ const Footer = ({
3031
selectedCount,
3132
onSelectedClick,
3233
hasHitSelectionLimit,
34+
isSingleSelect,
3335
onCancel,
3436
onChoose,
3537
chooseButtonLabel,
@@ -38,18 +40,20 @@ const Footer = ({
3840
}: Props) => (
3941
<footer className="bcp-footer">
4042
<div className="bcp-footer-left">
41-
<Button className="bcp-selected" onClick={onSelectedClick} type="button">
42-
<FormattedMessage
43-
className="bcp-selected-count"
44-
{...messages.selected}
45-
values={{ count: selectedCount }}
46-
/>
47-
{hasHitSelectionLimit && (
48-
<span className="bcp-selected-max">
49-
(<FormattedMessage {...messages.max} />)
50-
</span>
51-
)}
52-
</Button>
43+
{!isSingleSelect && (
44+
<Button className="bcp-selected" onClick={onSelectedClick} type="button">
45+
<FormattedMessage
46+
className="bcp-selected-count"
47+
{...messages.selected}
48+
values={{ count: selectedCount }}
49+
/>
50+
{hasHitSelectionLimit && (
51+
<span className="bcp-selected-max">
52+
(<FormattedMessage {...messages.max} />)
53+
</span>
54+
)}
55+
</Button>
56+
)}
5357
</div>
5458
<div className="bcp-footer-right">
5559
{children}

src/elements/content-picker/ItemList.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import nameCellRenderer from '../common/item/nameCellRenderer';
1313
import iconCellRenderer from '../common/item/iconCellRenderer';
1414
import { isFocusableElement, focus } from '../../utils/dom';
1515
import shareAccessCellRenderer from './shareAccessCellRenderer';
16-
import checkboxCellRenderer from './checkboxCellRenderer';
16+
import selectionCellRenderer from './selectionCellRenderer';
1717
import isRowSelectable from './cellRendererHelper';
1818
import { VIEW_SELECTED, FIELD_NAME, FIELD_ID, FIELD_SHARED_LINK, TYPE_FOLDER } from '../../constants';
1919

@@ -25,6 +25,7 @@ type Props = {
2525
extensionsWhitelist: string[],
2626
focusedRow: number,
2727
hasHitSelectionLimit: boolean,
28+
isSingleSelect: boolean,
2829
isSmall: boolean,
2930
onFocusChange: Function,
3031
onItemClick: Function,
@@ -46,6 +47,7 @@ const ItemList = ({
4647
selectableType,
4748
canSetShareAccess,
4849
hasHitSelectionLimit,
50+
isSingleSelect,
4951
extensionsWhitelist,
5052
onItemSelect,
5153
onItemClick,
@@ -56,7 +58,13 @@ const ItemList = ({
5658
}: Props) => {
5759
const iconCell = iconCellRenderer();
5860
const nameCell = nameCellRenderer(rootId, view, onItemClick);
59-
const checkboxCell = checkboxCellRenderer(onItemSelect, selectableType, extensionsWhitelist, hasHitSelectionLimit);
61+
const selectionCell = selectionCellRenderer(
62+
onItemSelect,
63+
selectableType,
64+
extensionsWhitelist,
65+
hasHitSelectionLimit,
66+
isSingleSelect,
67+
);
6068
const shareAccessCell = shareAccessCellRenderer(
6169
onShareAccessChange,
6270
canSetShareAccess,
@@ -154,7 +162,7 @@ const ItemList = ({
154162
)}
155163
<Column
156164
dataKey={FIELD_ID}
157-
cellRenderer={checkboxCell}
165+
cellRenderer={selectionCell}
158166
width={isSmall ? 20 : 30}
159167
flexShrink={0}
160168
/>

src/elements/content-picker/ItemList.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@
6161
}
6262
}
6363

64-
.checkbox-container {
65-
margin: 0;
64+
.checkbox-container,
65+
.radio-container {
66+
margin: 0 0 0 1px;
6667

6768
span,
6869
input {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import selectionCellRenderer from '../selectionCellRenderer';
4+
5+
const rowData = {
6+
name: 'test',
7+
selected: true,
8+
type: 'file',
9+
};
10+
11+
describe('selectionCellRenderer', () => {
12+
test.each([['Checkbox', false], ['RadioButton', true]])('should render %s if isRadio is %s', (type, isRadio) => {
13+
const Element = selectionCellRenderer(() => {}, 'file, web_link', [], false, isRadio);
14+
15+
const wrapper = shallow(<Element rowData={rowData} />);
16+
expect(wrapper.exists(type)).toBe(true);
17+
});
18+
19+
test.each([['isSelected', true], ['isChecked', false]])('should render %s if isRadio is %s', (type, isRadio) => {
20+
const Element = selectionCellRenderer(() => {}, 'file, web_link', [], false, isRadio);
21+
22+
const wrapper = shallow(<Element rowData={rowData} />);
23+
expect(wrapper.prop(type)).toBe(true);
24+
});
25+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @flow strict
3+
* @file Function to render the checkbox or radio table cell
4+
* @author Box
5+
*/
6+
7+
import React from 'react';
8+
import Checkbox from '../../components/checkbox';
9+
import RadioButton from '../../components/radio/RadioButton';
10+
import isRowSelectable from './cellRendererHelper';
11+
12+
export default (
13+
onItemSelect: (rowData: BoxItem) => {},
14+
selectableType: string,
15+
extensionsWhitelist: string[],
16+
hasHitSelectionLimit: boolean,
17+
isRadio: boolean,
18+
): (({ rowData: BoxItem }) => {}) => ({ rowData }: { rowData: BoxItem }) => {
19+
const { name = '', selected = false } = rowData;
20+
const Component = isRadio ? RadioButton : Checkbox;
21+
22+
if (!isRowSelectable(selectableType, extensionsWhitelist, hasHitSelectionLimit, rowData)) {
23+
return <span />;
24+
}
25+
26+
return (
27+
<Component
28+
hideLabel
29+
label={name}
30+
name={name}
31+
onChange={() => onItemSelect(rowData)}
32+
value={name}
33+
{...{ [isRadio ? 'isSelected' : 'isChecked']: selected }}
34+
/>
35+
);
36+
};

test/integration/content-picker/ContentPicker.e2e.test.js

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@ const helpers = {
1010
cy.getByTestId('be-sub-header').contains('Codepen');
1111
},
1212
getRow: rowNum => cy.getByTestId('content-picker').find(`.bcp-item-row-${rowNum}`),
13-
selectRow(rowNum) {
13+
selectRow(rowNum, rowType = 'checkbox') {
1414
this.getRow(rowNum)
1515
.as('row')
1616
.click()
17-
.find('input[type="checkbox"]')
17+
.find(`input[type="${rowType}"]`)
1818
.should('be.checked');
1919

2020
cy.get('@row').find('.bcp-shared-access-select');
2121

2222
return cy.get('@row');
2323
},
24-
unselectRow(rowNum) {
24+
unselectRow(rowNum, rowType = 'checkbox') {
2525
this.getRow(rowNum)
2626
.as('row')
2727
.click()
28-
.find('input[type="checkbox"]')
28+
.find(`input[type="${rowType}"]`)
2929
.should('not.be.checked');
3030

3131
cy.get('@row')
@@ -148,29 +148,25 @@ describe('ContentPicker', () => {
148148
helpers
149149
.getRow(2)
150150
.as('rowTwo')
151-
.find('input[type="checkbox"]')
151+
.find('input[type="radio"]')
152152
.should('not.be.checked');
153-
helpers.selectRow(2);
153+
helpers.selectRow(2, 'radio');
154154

155155
// Select row 3
156156
helpers
157157
.getRow(3)
158158
.as('rowThree')
159-
.find('input[type="checkbox"]')
159+
.find('input[type="radio"]')
160160
.should('not.be.checked');
161-
helpers.selectRow(3);
161+
helpers.selectRow(3, 'radio');
162162

163163
// Row 2 should now be unchecked
164164
cy.get('@rowTwo')
165-
.find('input[type="checkbox"]')
165+
.find('input[type="radio"]')
166166
.should('not.be.checked');
167167

168-
cy.contains('1 Selected');
169-
170168
// Unselect row 3
171-
helpers.unselectRow(3);
172-
173-
cy.contains('0 Selected');
169+
helpers.unselectRow(3, 'radio');
174170
});
175171
});
176172
});

0 commit comments

Comments
 (0)