Skip to content

Commit 8bfbd5a

Browse files
committed
Improve dependency search experience
1 parent 3add646 commit 8bfbd5a

File tree

3 files changed

+163
-60
lines changed

3 files changed

+163
-60
lines changed

packages/app/src/app/pages/Sandbox/SearchDependencies/RawAutoComplete/elements.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,15 @@ export const AutoCompleteInput = styled.input`
99
font-weight: 600;
1010
color: ${props => props.theme.white};
1111
padding: 0.75em 1em;
12+
z-index: 2;
13+
`;
14+
15+
export const SuggestionInput = styled(AutoCompleteInput)`
16+
position: absolute;
17+
top: 0;
18+
left: 0;
19+
color: rgba(255, 255, 255, 0.3);
20+
background-color: transparent;
21+
z-index: 1;
22+
pointer-events: none;
1223
`;

packages/app/src/app/pages/Sandbox/SearchDependencies/RawAutoComplete/index.js

Lines changed: 151 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,160 @@ import { Pagination } from 'react-instantsearch/dom';
66
import { ENTER } from 'common/utils/keycodes';
77

88
import DependencyHit from '../DependencyHit';
9-
import { AutoCompleteInput } from './elements';
10-
11-
function RawAutoComplete({
12-
onSelect,
13-
onManualSelect,
14-
onHitVersionChange,
15-
hits,
16-
refine,
17-
currentRefinement,
18-
}) {
19-
return (
20-
<Downshift itemToString={hit => (hit ? hit.name : hit)} onSelect={onSelect}>
21-
{({ getInputProps, getItemProps, highlightedIndex }) => (
22-
<div>
23-
<AutoCompleteInput
24-
autoFocus
25-
{...getInputProps({
26-
innerRef(ref) {
27-
if (ref) {
28-
if (
29-
document.activeElement &&
30-
document.activeElement.tagName !== 'SELECT'
31-
) {
32-
ref.focus();
33-
}
34-
}
35-
},
36-
value: currentRefinement,
37-
placeholder: 'Search or enter npm dependency',
38-
onChange(e) {
39-
refine(e.target.value);
40-
},
41-
onKeyUp(e) {
42-
// If enter with no selection
43-
if (e.keyCode === ENTER) {
44-
onManualSelect(e.target.value);
45-
}
46-
},
47-
})}
48-
/>
49-
<Pagination />
9+
import { AutoCompleteInput, SuggestionInput } from './elements';
10+
11+
/* eslint-disable no-param-reassign */
12+
13+
function getName(value: string) {
14+
const scope = value[0] === '@' ? '@' : '';
15+
value = scope ? value.substr(1) : value;
16+
17+
return scope + value.split('@')[0];
18+
}
19+
20+
function isExplicitVersion(value: string) {
21+
const scope = value[0] === '@' ? '@' : '';
22+
value = scope ? value.substr(1) : value;
23+
24+
return value.includes('@');
25+
}
26+
27+
function getVersion(value: string, hit) {
28+
return value.indexOf('@') > 0
29+
? value.split('@')[1]
30+
: hit
31+
? hit.version
32+
: null;
33+
}
34+
35+
function getIsValid(value: string, hit, version: string) {
36+
return Boolean(
37+
hit &&
38+
hit.name.startsWith(getName(value)) &&
39+
(version in hit.tags || version in hit.versions)
40+
);
41+
}
42+
43+
function getHit(value: string, hits) {
44+
return value && hits.find(hit => hit.name.startsWith(value));
45+
}
46+
47+
class RawAutoComplete extends React.Component {
48+
state = {
49+
value: '',
50+
};
51+
52+
render() {
53+
const {
54+
onSelect,
55+
onManualSelect,
56+
onHitVersionChange,
57+
hits,
58+
refine,
59+
currentRefinement,
60+
} = this.props;
61+
62+
const hit = getHit(currentRefinement, hits);
63+
const version = getVersion(this.state.value, hit);
64+
const isValid = getIsValid(this.state.value, hit, version);
5065

66+
return (
67+
<Downshift itemToString={h => (h ? h.name : h)} onSelect={onSelect}>
68+
{({ getInputProps, getItemProps, highlightedIndex }) => (
5169
<div>
52-
{hits.map((hit, index) => (
53-
<DependencyHit
54-
key={hit.name}
55-
{...getItemProps({
56-
item: hit,
57-
index,
58-
highlighted: highlightedIndex === index,
59-
hit,
60-
// Downshift supplies onClick
61-
onVersionChange(version) {
62-
onHitVersionChange(hit, version);
63-
},
64-
})}
65-
/>
66-
))}
70+
{highlightedIndex == null && (
71+
<SuggestionInput as="div">
72+
{isExplicitVersion(this.state.value)
73+
? this.state.value
74+
: hit
75+
? hit.name
76+
: currentRefinement}
77+
<span
78+
css={{
79+
color: 'var(--color-white-3)',
80+
}}
81+
>
82+
{isExplicitVersion(this.state.value)
83+
? null
84+
: hit && isValid
85+
? '@' + hit.version
86+
: null}
87+
</span>
88+
</SuggestionInput>
89+
)}
90+
<AutoCompleteInput
91+
autoFocus
92+
{...getInputProps({
93+
innerRef(ref) {
94+
if (ref) {
95+
if (
96+
document.activeElement &&
97+
document.activeElement.tagName !== 'SELECT'
98+
) {
99+
ref.focus();
100+
}
101+
}
102+
},
103+
value: this.state.value,
104+
placeholder: 'Search or enter npm dependency',
105+
106+
onChange: e => {
107+
const name = e.target.value;
108+
109+
this.setState({ value: name }, () => {
110+
if (name.indexOf('@') === 0) {
111+
const parts = name.split('@');
112+
113+
refine(`@${parts[1]}`);
114+
return;
115+
}
116+
117+
const parts = name.split('@');
118+
119+
requestAnimationFrame(() => {
120+
refine(`${parts[0]}`);
121+
});
122+
});
123+
},
124+
125+
onKeyUp: e => {
126+
// If enter with no selection
127+
if (e.keyCode === ENTER) {
128+
onManualSelect(
129+
isExplicitVersion(this.state.value)
130+
? e.target.value
131+
: hit && isValid
132+
? hit.name + '@' + hit.version
133+
: e.target.value
134+
);
135+
}
136+
},
137+
})}
138+
/>
139+
<Pagination />
140+
141+
<div>
142+
{hits.map((h, index) => (
143+
<DependencyHit
144+
key={h.name}
145+
{...getItemProps({
146+
item: h,
147+
index,
148+
highlighted: highlightedIndex === index,
149+
hit: h,
150+
// Downshift supplies onClick
151+
onVersionChange(v) {
152+
onHitVersionChange(h, v);
153+
},
154+
})}
155+
/>
156+
))}
157+
</div>
67158
</div>
68-
</div>
69-
)}
70-
</Downshift>
71-
);
159+
)}
160+
</Downshift>
161+
);
162+
}
72163
}
73164

74165
export default RawAutoComplete;

packages/app/src/app/pages/Sandbox/SearchDependencies/dependencies.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
border-radius: 0;
1111
display: flex;
1212
justify-content: center;
13+
padding: 0;
1314
}

0 commit comments

Comments
 (0)