Skip to content

Commit 80cf009

Browse files
committed
Merge pull request #260 from salesforce-ux/lookup-sections
Lookup sections
2 parents decbc13 + a1927db commit 80cf009

File tree

32 files changed

+1651
-15359
lines changed

32 files changed

+1651
-15359
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright (c) 2015, salesforce.com, inc. All rights reserved.
3+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
5+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
6+
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
7+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8+
*/
9+
10+
import React from 'react';
11+
import { EventUtil } from '../../../utils';
12+
13+
const displayName = "LookupDefaultSectionHeader";
14+
const propTypes = {
15+
data: React.PropTypes.object,
16+
key: React.PropTypes.string
17+
18+
};
19+
20+
class DefaultSectionHeader extends React.Component {
21+
constructor(props) {
22+
super(props);
23+
}
24+
25+
handleMouseDown(event) {
26+
EventUtil.trapImmediate(event);
27+
}
28+
29+
render(){
30+
return (
31+
<li className="slds-p-around--x-small"
32+
tabIndex="-1"
33+
>
34+
<span className="slds-m-left--x-small">
35+
<strong>{this.props.data.label}</strong>
36+
</span>
37+
</li>
38+
)
39+
}
40+
}
41+
42+
DefaultSectionHeader.displayName = displayName;
43+
DefaultSectionHeader.propTypes = propTypes;
44+
45+
module.exports = DefaultSectionHeader;

components/SLDSLookup/Menu/index.js

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import ReactDOM from 'react-dom';
1212

1313
import Item from './Item';
1414

15+
import DefaultSectionHeader from './DefaultSectionHeader';
16+
1517
const displayName = 'SLDSLookup-Menu';
1618
const propTypes = {
1719
boldRegex: React.PropTypes.instanceOf(RegExp),
@@ -32,23 +34,50 @@ const defaultProps = {
3234
class Menu extends React.Component {
3335
constructor(props){
3436
super(props);
35-
this.state = {};
37+
this.state = {filteredItems:this.filteredItems()};
3638
}
3739

3840
//Set filtered list length in parent to determine active indexes for aria-activedescendent
39-
componentDidUpdate(){
41+
componentDidUpdate(prevProps){
4042
// make an array of the children of the list but only count the actual items
4143
let list = [].slice.call(ReactDOM.findDOMNode(this.refs.list).children)
4244
.filter((child) => child.className.indexOf("slds-lookup__item") > -1).length;
4345
this.props.getListLength(list);
46+
if(
47+
prevProps.items !== this.props.items ||
48+
prevProps.filter !== this.props.filter ||
49+
prevProps.searchTerm !== this.props.searchTerm
50+
){
51+
this.setState({
52+
filteredItems:this.filteredItems()
53+
});
54+
}
4455
}
4556

4657
filter(item){
4758
return this.props.filterWith(this.props.searchTerm, item);
4859
}
4960

5061
filteredItems() {
51-
return this.props.items.filter(this.filter, this)
62+
return this.filterEmptySections(this.props.items.filter(this.filter, this));
63+
}
64+
65+
filterEmptySections(items){
66+
const result = [];
67+
items.forEach((item,index) => {
68+
if(item && item.data && item.data.type === 'section'){
69+
if(index+1<items.length){
70+
const nextItem = items[index+1];
71+
if(nextItem.data && nextItem.data.type !== 'section'){
72+
result.push(item);
73+
}
74+
}
75+
}
76+
else{
77+
result.push(item);
78+
}
79+
});
80+
return result;
5281
}
5382

5483
//Scroll menu up/down when using mouse keys
@@ -58,6 +87,12 @@ class Menu extends React.Component {
5887
}
5988
}
6089

90+
getFilteredItemForIndex(i){
91+
if(i>-1 && this.state.filteredItems && i< this.state.filteredItems.length){
92+
return this.state.filteredItems[i];
93+
}
94+
}
95+
6196
renderHeader(){
6297
return this.props.header;
6398
}
@@ -67,14 +102,18 @@ class Menu extends React.Component {
67102
}
68103

69104
renderItems(){
70-
return this.filteredItems().map((c, i) => {
105+
let focusIndex = this.props.focusIndex;
106+
return this.state.filteredItems.map((c, i) => {
71107
//isActive means it is aria-activedescendant
72108
const id = c.id;
73109
let isActive = false;
74110
if (this.props.header) {
75-
isActive = this.props.focusIndex === i + 1 ? true : false;
111+
isActive = focusIndex === i + 1 ? true : false;
76112
}else{
77-
isActive = this.props.focusIndex === i ? true : false;
113+
isActive = focusIndex === i ? true : false;
114+
}
115+
if(c.data.type==='section'){
116+
return <DefaultSectionHeader data={c.data} key={'section_header_'+i}/>;
78117
}
79118
return <Item
80119
boldRegex={this.props.boldRegex}
@@ -98,7 +137,7 @@ class Menu extends React.Component {
98137
}
99138

100139
renderContent(){
101-
if (this.filteredItems().length === 0)
140+
if (this.state.filteredItems.length === 0)
102141
return (
103142
<li className="slds-lookup__message" aria-live="polite">
104143
<span className="slds-m-left--x-large slds-p-vertical--medium">{this.props.emptyMessage}</span>

components/SLDSLookup/index.jsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const propTypes = {
9595
*/
9696
const defaultFilter = (term, item) => {
9797
if(!term) return true;
98-
return item.label.match(new RegExp(escapeRegExp(term), "ig"));
98+
return (item.data && item.data.type === 'section') || item.label.match(new RegExp(escapeRegExp(term), "ig"));
9999
};
100100

101101

@@ -162,17 +162,40 @@ class SLDSLookup extends React.Component {
162162
this.setState({items: items});
163163
}
164164

165+
setFirstIndex(){
166+
let numFocusable = this.getNumFocusableItems();
167+
let nextFocusIndex = 0;
168+
let filteredItem = this.state.items[0];
169+
if(this.refs.menu && this.refs.menu.getFilteredItemForIndex){
170+
filteredItem = this.refs.menu.getFilteredItemForIndex(nextFocusIndex);
171+
}
172+
if(filteredItem && filteredItem.data.type === 'section'){
173+
nextFocusIndex++;
174+
}
175+
this.setState({ focusIndex: nextFocusIndex });
176+
}
165177
//=================================================
166178
// Using down/up keys, set Focus on list item and assign it to aria-activedescendant attribute in input.
167179
// Need to keep track of filtered list length to be able to increment/decrement the focus index so it's contained to the number of available list items.
168180
increaseIndex() {
169181
let numFocusable = this.getNumFocusableItems();
170-
this.setState({ focusIndex: this.state.focusIndex < numFocusable ? this.state.focusIndex + 1 : 0 });
182+
let nextFocusIndex = this.state.focusIndex < numFocusable ? this.state.focusIndex + 1 : 0;
183+
const filteredItem = this.refs.menu.getFilteredItemForIndex(nextFocusIndex);
184+
if(filteredItem && filteredItem.data.type === 'section'){
185+
nextFocusIndex++;
186+
}
187+
this.setState({ focusIndex: nextFocusIndex });
171188
}
172189

173190
decreaseIndex() {
174191
let numFocusable = this.getNumFocusableItems();
175-
this.setState({ focusIndex: this.state.focusIndex > 0 ? this.state.focusIndex - 1 : numFocusable});
192+
let prevFocusIndex = this.state.focusIndex > 0 ? this.state.focusIndex - 1 : numFocusable;
193+
const filteredItem = this.refs.menu.getFilteredItemForIndex(prevFocusIndex);
194+
console.log('filteredItem', filteredItem);
195+
if(filteredItem && filteredItem.data.type === 'section'){
196+
prevFocusIndex === 0 ? prevFocusIndex = numFocusable : prevFocusIndex--;
197+
}
198+
this.setState({ focusIndex: prevFocusIndex});
176199
}
177200

178201
setFocus(id) {
@@ -285,7 +308,7 @@ class SLDSLookup extends React.Component {
285308
//If user hits down key, advance aria activedescendant to next item
286309
if(event.keyCode === KEYS.DOWN){
287310
EventUtil.trapImmediate(event);
288-
this.state.focusIndex === null ? this.setState({ focusIndex: 0 }) : this.increaseIndex();
311+
this.state.focusIndex === null ? this.setFirstIndex() : this.increaseIndex();
289312
}
290313
//If user hits up key, advance aria activedescendant to previous item
291314
else if(event.keyCode === KEYS.UP){
@@ -362,6 +385,7 @@ class SLDSLookup extends React.Component {
362385
renderMenuContent() {
363386
if(this.state.isOpen){
364387
return <Menu
388+
ref="menu"
365389
emptyMessage={this.props.emptyMessage}
366390
filterWith={this.props.filterWith}
367391
focusIndex={this.state.focusIndex}

demo/code-snippets/LookupExamples3.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
onChange={function(newValue){console.log("New search term: ", newValue)}}
66
onSelect={function(item){console.log(item , " Selected")}}
77
options={[
8+
{type:'section', label:'SECTION 1'},
89
{label: "Paddy\"s Pub"},
910
{label: "Tyrell Corp"},
11+
{type:'section', label:'SECTION 2'},
1012
{label: "Paper St. Soap Company"},
1113
{label: "Nakatomi Investments"},
1214
{label: "Acme Landscaping"},
15+
{type:'section', label:'SECTION 3'},
1316
{label: "Acme Construction"}
1417
]}
1518
/>
19+

0 commit comments

Comments
 (0)