Skip to content

Commit 9265e54

Browse files
authored
Closes #95, Add support for multiple highlights (#97)
* handle multiple keys * add unit tests * add docs * remove important from css * refactor suggestions to reduce cognitive complexity
1 parent 8a112ed commit 9265e54

8 files changed

+114
-36
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ const commands = [{
7171
* ```options``` options controls how fuzzy search is configured. Note: use at your own risk, this is likley to change in the future. The search options are derived from these [fuzzysort options](https://github.com/farzher/fuzzysort#options). However the command palette options prop must have the following values included to function correctly:
7272

7373
```js
74-
key: "name", // must be "name"
75-
keys: ["name"], // must include "name"
74+
key: "name", // default is "name"
75+
keys: ["name"], // default is "name"
7676
7777
// other options may be freely configured
7878
threshold: -Infinity,
@@ -126,7 +126,7 @@ const commands = [{
126126
```
127127
see: https://github.com/moroshko/react-autosuggest#rendersuggestion-required.
128128
129-
Note: the _suggestion.hightlight_ will be passed and contains the rendered markup from (fuzzysort)[farzher/fuzzysort#fuzzysorthighlightresult-openb-closeb], see the ```options``` prop.
129+
Note: the _suggestion.hightlight_ will be passed and contains the rendered markup from [fuzzysort](farzher/fuzzysort#fuzzysorthighlightresult-openb-closeb), see the ```options``` prop. If the ```options``` prop contains an array of "keys" then an highlights will contain an array of matches, see: [fuzzysort advanced usage](https://github.com/farzher/fuzzysort#advanced-usage) or checkout the [sampleChromeCommand.js](examples/sampleChromeCommand.js)
130130
131131
See [a full example](examples/sampleAtomCommand.js)
132132

examples/sampleChromeCommand.css

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
.chrome-category {
2-
color: #fff;
2+
color: rgb(255, 255, 255);
33
margin-right: 6px;
44
border-radius: 2px;
55
padding: 1.2px 3px;
66
}
77

8+
.chrome-category > b {
9+
color: rgb(255, 255, 255);
10+
font-weight: bolder;
11+
}
12+
813
.chrome-shortcut {
914
float: right;
1015
margin-right: 2px;

examples/sampleChromeCommand.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@ import React from "react";
22
import "./sampleChromeCommand.css";
33

44
export default function sampleChromeCommand(suggestion) {
5-
const { name, highlight, category, shortcut } = suggestion;
5+
const { name, highlight = [], category, shortcut } = suggestion;
6+
7+
// handle simple highlight when searching a single key
8+
if (!Array.isArray(highlight)) {
9+
return (
10+
<div className="chrome-suggestion">
11+
<span className={`chrome-category ${category}`}>{category}</span>
12+
<span dangerouslySetInnerHTML={{ __html: highlight || name }} />
13+
<kbd className="chrome-shortcut">{shortcut}</kbd>
14+
</div>
15+
);
16+
}
17+
18+
// handle multiple highlights when searching multiple keys, see:
19+
// https://github.com/farzher/fuzzysort#advanced-usage
20+
// Note that we are passing "keys" to the fuzzysort options, ex:
21+
// <CommandPalette
22+
// commands={commands}
23+
// options={{keys: ["name", "category"]}}
24+
// renderCommand={sampleChromeCommand}
25+
// />
626
return (
727
<div className="chrome-suggestion">
8-
<span className={`chrome-category ${category}`}>{category}</span>
9-
{highlight ? (
10-
<span dangerouslySetInnerHTML={{ __html: highlight }} />
11-
) : (
12-
<span>{name}</span>
13-
)}
28+
<span
29+
dangerouslySetInnerHTML={{ __html: highlight[1] || category }}
30+
className={`chrome-category ${category}`}
31+
/>
32+
<span dangerouslySetInnerHTML={{ __html: highlight[0] || name }} />
1433
<kbd className="chrome-shortcut">{shortcut}</kbd>
1534
</div>
1635
);

src/render-command.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ RenderCommand.propTypes = {
1818
/** a single suggestion that appears in the command palette. It must have a _name_ and
1919
* a _command_. The _name_ is a user friendly string that will be display to the user.
2020
* The command is a function that will be executed when the user clicks or presses the
21-
* enter key. */
21+
* enter key. For single match a _highlight_ string will be passed, for mutliple mathes the _highlight_ should be an array */
2222
suggestion: PropTypes.shape({
2323
name: PropTypes.string.isRequired,
24-
highlight: PropTypes.string,
24+
highlight: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
2525
command: PropTypes.func.isRequired
2626
}),
2727

src/suggestions.js

+34-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import fuzzysort from "fuzzysort";
22

3-
// Teach Autosuggest how to calculate suggestions for any given input value.
4-
export default function getSuggestions(value = "", allCommands, options) {
5-
// return all commands when user didnt suggest a specific term
6-
if (!value) {
7-
return allCommands;
3+
function getSuggestionHighlights(suggestion) {
4+
// if there's more than one suggestion, retun an array of
5+
// highlighted results. ex: ["first *result*", "second *result*"]
6+
if (Array.isArray(suggestion) && suggestion.length >= 2) {
7+
return suggestion.map(result => fuzzysort.highlight(result));
88
}
9+
// otherwise return the single suggestion as a string. ex:
10+
// "only *result*"
11+
return fuzzysort.highlight(suggestion[0]);
12+
}
13+
14+
// format the output to include a code higlight for innerHTML
15+
// and the command to invoke
16+
function formatSuggestions(filteredSuggestions) {
17+
return filteredSuggestions.map(suggestion => {
18+
const opts = {
19+
name: suggestion.obj.name,
20+
command: suggestion.obj.command,
21+
highlight: getSuggestionHighlights(suggestion)
22+
};
23+
return { ...opts, ...suggestion.obj };
24+
});
25+
}
926

27+
// Teach Autosuggest how to calculate suggestions for any given input value.
28+
const getSuggestions = function(value, allCommands, options) {
1029
// TODO: preparing fuzzysort results make them much faster
1130
// however prepare is expensiveand should only be run when
1231
// the commands change lodash.once get close to this
@@ -18,23 +37,16 @@ export default function getSuggestions(value = "", allCommands, options) {
1837

1938
// If the user specified an autosuggest term
2039
// search for close matches
21-
const filteredSuggestions = fuzzysort.go(value, allCommands, options);
40+
const suggestionResults = fuzzysort.go(value, allCommands, options);
2241

23-
// format the output to include a code higlight for innerHTML
24-
// and the command to invoke
25-
const formattedSuggestions = filteredSuggestions.map(suggestion => {
26-
const opts = {
27-
name: suggestion.obj.name,
28-
command: suggestion.obj.command,
29-
highlight: fuzzysort.highlight(suggestion[0])
30-
};
31-
return { ...opts, ...suggestion.obj };
32-
});
33-
34-
// When the user specified a search term but there we no matches found
35-
// return all the commands
36-
if (!formattedSuggestions.length) return allCommands;
42+
// if the user didnt suggest a specific term or there's a search term
43+
// but no matches were found return all the commands
44+
if (!value || !suggestionResults.length) {
45+
return allCommands;
46+
}
3747

3848
// Otherwise return the search results
39-
return formattedSuggestions;
40-
}
49+
return formatSuggestions(suggestionResults);
50+
};
51+
52+
export default getSuggestions;

src/suggestions.test.js

+27
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,31 @@ describe("getSuggestions", () => {
3535
expect(commands.length).toBe(2);
3636
});
3737
});
38+
39+
describe("a value was provide with mutiple keys to search", () => {
40+
it("should return the matching command with multple highlights", () => {
41+
fuzzysortOptions.keys = ["name", "category"];
42+
const matchName = getSuggestions(
43+
"Imports",
44+
allCommands,
45+
fuzzysortOptions
46+
);
47+
expect(matchName[0]).toMatchObject({
48+
name: "Stop All Data Imports",
49+
category: "Command",
50+
highlight: ["Stop All Data <b>Imports</b>", null]
51+
});
52+
53+
const matchCategory = getSuggestions(
54+
"Com",
55+
allCommands,
56+
fuzzysortOptions
57+
);
58+
expect(matchCategory[0]).toMatchObject({
59+
name: "Stop All Data Imports",
60+
category: "Command",
61+
highlight: [null, "<b>Com</b>mand"]
62+
});
63+
});
64+
});
3865
});

stories/index.stories.js

+15
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,19 @@ storiesOf("Command Palette", module)
259259
open
260260
/>
261261
);
262+
})
263+
.add("with multiple highlights", () => {
264+
const opts = {
265+
keys: ["name", "category"]
266+
};
267+
return (
268+
<CommandPalette
269+
commands={commands}
270+
options={opts}
271+
maxDisplayed={10}
272+
renderCommand={sampleChromeCommand}
273+
theme={chrome}
274+
open
275+
/>
276+
);
262277
});

themes/chrome.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
cursor: pointer;
8080
}
8181

82-
.chrome-suggestion b {
82+
.chrome-suggestion > b {
8383
color: rgb(30, 30, 30);
8484
font-weight: bold;
8585
}

0 commit comments

Comments
 (0)