Skip to content

Commit d209d2e

Browse files
Merge pull request salesforce#1645 from kevinparkerson/listbox-of-pills-component
Adds Listbox of Pill Options component (called "Pill Container")
2 parents af783fb + 2913ee0 commit d209d2e

38 files changed

+2441
-1917
lines changed

components/combobox/__tests__/__snapshots__/combobox.snapshot-test.jsx.snap

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,7 @@ exports[`Base Selected DOM Snapshot 1`] = `
11831183
className="slds-pill"
11841184
onBlur={[Function]}
11851185
onClick={[Function]}
1186+
onFocus={[Function]}
11861187
onKeyDown={[Function]}
11871188
role="option"
11881189
tabIndex="0"
@@ -1191,7 +1192,7 @@ exports[`Base Selected DOM Snapshot 1`] = `
11911192
className="slds-pill__icon_container"
11921193
>
11931194
<span
1194-
className="slds-icon_container slds-icon-standard-account slds-pill__icon_container"
1195+
className="slds-icon_container slds-icon-standard-account"
11951196
>
11961197
<svg
11971198
aria-hidden="true"
@@ -1257,7 +1258,7 @@ exports[`Base Selected HTML Snapshot 1`] = `
12571258
</div>
12581259
<div id=\\"combobox-unique-id-selected-listbox\\" role=\\"listbox\\" aria-orientation=\\"horizontal\\">
12591260
<ul class=\\"slds-listbox slds-listbox_horizontal slds-p-top_xxx-small\\" role=\\"group\\" aria-label=\\"Selected Options:\\">
1260-
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"0\\" aria-selected=\\"true\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account slds-pill__icon_container\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
1261+
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"0\\" aria-selected=\\"true\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
12611262
</span><span class=\\"slds-pill__label\\" title=\\"Salesforce.com, Inc.\\">Salesforce.com, Inc.</span><span class=\\"slds-icon_container slds-pill__remove\\" title=\\"Remove\\" role=\\"button\\"><svg style=\\"cursor:pointer\\" class=\\"slds-icon slds-icon_x-small slds-icon-text-default\\"><use xlink:href=\\"/assets/icons/utility-sprite/svg/symbols.svg#close\\"></use></svg><span class=\\"slds-assistive-text\\">, Press delete or backspace to remove</span></span>
12621263
</span>
12631264
</li>
@@ -1377,6 +1378,7 @@ exports[`Inline Multiple Selection Selected DOM Snapshot 1`] = `
13771378
className="slds-pill"
13781379
onBlur={[Function]}
13791380
onClick={[Function]}
1381+
onFocus={[Function]}
13801382
onKeyDown={[Function]}
13811383
role="option"
13821384
tabIndex="0"
@@ -1385,7 +1387,7 @@ exports[`Inline Multiple Selection Selected DOM Snapshot 1`] = `
13851387
className="slds-pill__icon_container"
13861388
>
13871389
<span
1388-
className="slds-icon_container slds-icon-standard-account slds-pill__icon_container"
1390+
className="slds-icon_container slds-icon-standard-account"
13891391
>
13901392
<svg
13911393
aria-hidden="true"
@@ -1443,6 +1445,7 @@ exports[`Inline Multiple Selection Selected DOM Snapshot 1`] = `
14431445
className="slds-pill"
14441446
onBlur={[Function]}
14451447
onClick={[Function]}
1448+
onFocus={[Function]}
14461449
onKeyDown={[Function]}
14471450
role="option"
14481451
tabIndex={-1}
@@ -1451,7 +1454,7 @@ exports[`Inline Multiple Selection Selected DOM Snapshot 1`] = `
14511454
className="slds-pill__icon_container"
14521455
>
14531456
<span
1454-
className="slds-icon_container slds-icon-standard-account slds-pill__icon_container"
1457+
className="slds-icon_container slds-icon-standard-account"
14551458
>
14561459
<svg
14571460
aria-hidden="true"
@@ -1554,11 +1557,11 @@ exports[`Inline Multiple Selection Selected HTML Snapshot 1`] = `
15541557
<div class=\\"slds-combobox_container slds-has-inline-listbox\\">
15551558
<div id=\\"combobox-unique-id-selected-listbox\\" role=\\"listbox\\" aria-orientation=\\"horizontal\\">
15561559
<ul class=\\"slds-listbox slds-listbox_horizontal slds-p-top_xxx-small\\" role=\\"group\\" aria-label=\\"Selected Options:\\">
1557-
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"0\\" aria-selected=\\"true\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account slds-pill__icon_container\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
1560+
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"0\\" aria-selected=\\"true\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
15581561
</span><span class=\\"slds-pill__label\\" title=\\"Acme\\">Acme</span><span class=\\"slds-icon_container slds-pill__remove\\" title=\\"Remove\\" role=\\"button\\"><svg style=\\"cursor:pointer\\" class=\\"slds-icon slds-icon_x-small slds-icon-text-default\\"><use xlink:href=\\"/assets/icons/utility-sprite/svg/symbols.svg#close\\"></use></svg><span class=\\"slds-assistive-text\\">, Press delete or backspace to remove</span></span>
15591562
</span>
15601563
</li>
1561-
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"-1\\" aria-selected=\\"false\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account slds-pill__icon_container\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
1564+
<li role=\\"presentation\\" class=\\"slds-listbox__item\\"><span tabindex=\\"-1\\" aria-selected=\\"false\\" role=\\"option\\" class=\\"slds-pill\\"><span class=\\"slds-pill__icon_container\\"><span class=\\"slds-icon_container slds-icon-standard-account\\"><svg aria-hidden=\\"true\\" class=\\"slds-icon\\"><use xlink:href=\\"/assets/icons/standard-sprite/svg/symbols.svg#account\\"></use></svg><span class=\\"slds-assistive-text\\">Account</span></span>
15621565
</span><span class=\\"slds-pill__label\\" title=\\"Salesforce.com, Inc.\\">Salesforce.com, Inc.</span><span class=\\"slds-icon_container slds-pill__remove\\" title=\\"Remove\\" role=\\"button\\"><svg style=\\"cursor:pointer\\" class=\\"slds-icon slds-icon_x-small slds-icon-text-default\\"><use xlink:href=\\"/assets/icons/utility-sprite/svg/symbols.svg#close\\"></use></svg><span class=\\"slds-assistive-text\\">, Press delete or backspace to remove</span></span>
15631566
</span>
15641567
</li>
@@ -1913,6 +1916,7 @@ exports[`Readonly Multiple Selection Multiple Items Selected DOM Snapshot 1`] =
19131916
className="slds-pill"
19141917
onBlur={[Function]}
19151918
onClick={[Function]}
1919+
onFocus={[Function]}
19161920
onKeyDown={[Function]}
19171921
role="option"
19181922
tabIndex="0"
@@ -1958,6 +1962,7 @@ exports[`Readonly Multiple Selection Multiple Items Selected DOM Snapshot 1`] =
19581962
className="slds-pill"
19591963
onBlur={[Function]}
19601964
onClick={[Function]}
1965+
onFocus={[Function]}
19611966
onKeyDown={[Function]}
19621967
role="option"
19631968
tabIndex={-1}

components/combobox/combobox.jsx

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import InnerInput from '../../components/input/private/inner-input';
2323
import InputIcon from '../icon/input-icon';
2424
import Menu from './private/menu';
2525
import Label from '../forms/private/label';
26-
import SelectedListBox from './private/selected-listbox';
26+
import SelectedListBox from '../pill-container/private/selected-listbox';
2727

2828
import KEYS from '../../utilities/key-code';
2929
import KeyBuffer from '../../utilities/key-buffer';
@@ -242,6 +242,10 @@ const propTypes = {
242242
type: PropTypes.string,
243243
})
244244
).isRequired,
245+
/**
246+
* This callback exposes the selected listbox reference / DOM node to parent components.
247+
*/
248+
selectedListboxRef: PropTypes.func,
245249
/**
246250
* Value of input. _This is a controlled component,_ so you will need to control the input value by passing the `value` from `onChange` to a parent component or state manager, and then pass it back into the componet with this prop. Please see examples for more clarification. _Tested with snapshot testing._
247251
*/
@@ -281,17 +285,19 @@ class Combobox extends React.Component {
281285
super(props);
282286

283287
this.state = {
284-
isOpen: false,
285288
activeOption: undefined,
286289
activeOptionIndex: -1,
287290
// seeding initial state with this.props.selection[0]
288291
activeSelectedOption:
289292
(this.props.selection && this.props.selection[0]) || undefined,
290293
activeSelectedOptionIndex: 0,
294+
listboxHasFocus: false,
295+
isOpen: false,
291296
};
292297

293298
this.menuKeyBuffer = new KeyBuffer();
294299
this.menuRef = undefined;
300+
this.selectedListboxRef = null;
295301
}
296302

297303
/**
@@ -418,6 +424,13 @@ class Combobox extends React.Component {
418424
}
419425
};
420426

427+
setSelectedListboxRef = (ref) => {
428+
this.selectedListboxRef = ref;
429+
if (this.props.selectedListboxRef) {
430+
this.props.selectedListboxRef(ref);
431+
}
432+
};
433+
421434
handleBlurPill = () => {
422435
this.setState({ listboxHasFocus: false });
423436
};
@@ -516,6 +529,7 @@ class Combobox extends React.Component {
516529
};
517530

518531
if (this.props.variant === 'readonly') {
532+
callbacks[KEYS.TAB] = { callback: this.handleKeyDownTab };
519533
callbacks.other = { callback: this.handleKeyDownOther };
520534
}
521535

@@ -532,6 +546,14 @@ class Combobox extends React.Component {
532546
this.handleNavigateListboxMenu(event, { direction: 'next' });
533547
};
534548

549+
handleKeyDownTab = () => {
550+
if (this.selectedListboxRef) {
551+
this.setState({
552+
listboxHasFocus: true,
553+
});
554+
}
555+
};
556+
535557
handleKeyDownUp = (event) => {
536558
// Don't open if user is selecting text
537559
if (!event.shiftKey && this.state.isOpen) {
@@ -586,7 +608,7 @@ class Combobox extends React.Component {
586608
});
587609
};
588610

589-
handleNavigateListboxOfPills = (event, { direction }) => {
611+
handleNavigateSelectedListbox = (event, { direction }) => {
590612
const offsets = { next: 1, previous: -1 };
591613
this.setState((prevState) => {
592614
const isLastOptionAndRightIsPressed =
@@ -647,7 +669,7 @@ class Combobox extends React.Component {
647669
}
648670
};
649671

650-
handlePillClickListboxOfPills = (event, { option, index }) => {
672+
handlePillClickSelectedListbox = (event, { option, index }) => {
651673
// this is clicking the span, not the remove button
652674
this.setState({
653675
activeSelectedOption: option,
@@ -656,8 +678,14 @@ class Combobox extends React.Component {
656678
});
657679
};
658680

681+
handlePillFocus = () => {
682+
if (!this.state.listboxHasFocus) {
683+
this.setState({ listboxHasFocus: true });
684+
}
685+
};
686+
659687
/**
660-
* Selected options with listbox of pills event methods
688+
* Selected options with selected listbox event methods
661689
*/
662690

663691
handleRemoveSelectedOption = (event, { option, index }) => {
@@ -707,7 +735,7 @@ class Combobox extends React.Component {
707735
}
708736
};
709737

710-
handleRequestFocusListboxOfPills = (event, { ref }) => {
738+
handleRequestFocusSelectedListbox = (event, { ref }) => {
711739
if (ref) {
712740
this.activeSelectedOptionRef = ref;
713741
this.activeSelectedOptionRef.focus();
@@ -852,14 +880,16 @@ class Combobox extends React.Component {
852880
assistiveText={assistiveText}
853881
events={{
854882
onBlurPill: this.handleBlurPill,
855-
onClickPill: this.handlePillClickListboxOfPills,
856-
onRequestFocus: this.handleRequestFocusListboxOfPills,
857-
onRequestFocusOnNextPill: this.handleNavigateListboxOfPills,
858-
onRequestFocusOnPreviousPill: this.handleNavigateListboxOfPills,
883+
onClickPill: this.handlePillClickSelectedListbox,
884+
onPillFocus: this.handlePillFocus,
885+
onRequestFocus: this.handleRequestFocusSelectedListbox,
886+
onRequestFocusOnNextPill: this.handleNavigateSelectedListbox,
887+
onRequestFocusOnPreviousPill: this.handleNavigateSelectedListbox,
859888
onRequestRemove: this.handleRemoveSelectedOption,
860889
}}
861-
id={this.getId()}
890+
id={`${this.getId()}-selected-listbox`}
862891
labels={labels}
892+
selectedListboxRef={this.setSelectedListboxRef}
863893
selection={props.selection}
864894
listboxHasFocus={this.state.listboxHasFocus}
865895
/>
@@ -890,14 +920,16 @@ class Combobox extends React.Component {
890920
assistiveText={assistiveText}
891921
events={{
892922
onBlurPill: this.handleBlurPill,
893-
onClickPill: this.handlePillClickListboxOfPills,
894-
onRequestFocus: this.handleRequestFocusListboxOfPills,
895-
onRequestFocusOnNextPill: this.handleNavigateListboxOfPills,
896-
onRequestFocusOnPreviousPill: this.handleNavigateListboxOfPills,
923+
onClickPill: this.handlePillClickSelectedListbox,
924+
onPillFocus: this.handlePillFocus,
925+
onRequestFocus: this.handleRequestFocusSelectedListbox,
926+
onRequestFocusOnNextPill: this.handleNavigateSelectedListbox,
927+
onRequestFocusOnPreviousPill: this.handleNavigateSelectedListbox,
897928
onRequestRemove: this.handleRemoveSelectedOption,
898929
}}
899-
id={this.getId()}
930+
id={`${this.getId()}-selected-listbox`}
900931
labels={labels}
932+
selectedListboxRef={this.setSelectedListboxRef}
901933
selection={props.selection}
902934
listboxHasFocus={this.state.listboxHasFocus}
903935
/>
@@ -1220,14 +1252,16 @@ class Combobox extends React.Component {
12201252
assistiveText={assistiveText}
12211253
events={{
12221254
onBlurPill: this.handleBlurPill,
1223-
onClickPill: this.handlePillClickListboxOfPills,
1224-
onRequestFocus: this.handleRequestFocusListboxOfPills,
1225-
onRequestFocusOnNextPill: this.handleNavigateListboxOfPills,
1226-
onRequestFocusOnPreviousPill: this.handleNavigateListboxOfPills,
1255+
onClickPill: this.handlePillClickSelectedListbox,
1256+
onPillFocus: this.handlePillFocus,
1257+
onRequestFocus: this.handleRequestFocusSelectedListbox,
1258+
onRequestFocusOnNextPill: this.handleNavigateSelectedListbox,
1259+
onRequestFocusOnPreviousPill: this.handleNavigateSelectedListbox,
12271260
onRequestRemove: this.handleRemoveSelectedOption,
12281261
}}
1229-
id={this.getId()}
1262+
id={`${this.getId()}-selected-listbox`}
12301263
labels={labels}
1264+
selectedListboxRef={this.setSelectedListboxRef}
12311265
selection={props.selection}
12321266
listboxHasFocus={this.state.listboxHasFocus}
12331267
variant={this.props.variant}

0 commit comments

Comments
 (0)