Skip to content

Commit

Permalink
UPDATED/REBASED 2020-12-14
Browse files Browse the repository at this point in the history
initial version of search suggestions dropdown, removed from #1648
  • Loading branch information
codebykat committed Dec 14, 2020
1 parent 3fc99fa commit 3e18a4b
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 28 deletions.
87 changes: 59 additions & 28 deletions lib/search-field/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React, { Component, createRef, FormEvent, KeyboardEvent } from 'react';
import React, { Component, createRef, Fragment, FormEvent, KeyboardEvent } from 'react';
import { connect } from 'react-redux';
import SmallCrossIcon from '../icons/cross-small';
import { State } from '../state';
import { search } from '../state/ui/actions';
import SearchSuggestions from '../search-suggestions';

import { registerSearchField } from '../state/ui/search-field-middleware';

import type * as S from '../state';
import type * as T from '../types';

const KEY_ESC = 27;
const KEY_ENTER = 13;
const KEY_ARROW_UP = 38;
const KEY_ARROW_DOWN = 40;

type StateProps = {
openedTag: T.Tag | null;
searchQuery: string;
searchSelected: false;
showTrash: boolean;
};

Expand Down Expand Up @@ -51,51 +56,77 @@ export class SearchField extends Component<Props> {
this.inputField.current.focus();
};

interceptEsc = (event: KeyboardEvent) => {
if (KEY_ESC === event.keyCode) {
if (this.props.searchQuery === '') {
this.blur();
}
this.clearQuery();
}
doSearch = (query: string) => {
this.setState({ query, searchSelected: true });
this.onSearch(query);
};

// interceptKeys = event => {
// switch (event.keyCode) {
// case KEY_ESC:
// if (this.state.query === '') {
// this.inputField.blur();
// }
// return this.clearQuery();

// case KEY_ENTER:
// return this.keyHandler.select();

// case KEY_ARROW_DOWN:
// return this.keyHandler.next();

// case KEY_ARROW_UP:
// return this.keyHandler.prev();
// }
// };

update = ({
currentTarget: { value: query },
}: FormEvent<HTMLInputElement>) => {
this.props.onSearch(query);
this.setState({ query: encodeURIComponent(query), searchSelected: false });
};

clearQuery = () => this.props.onSearch('');

render() {
const { openedTag, searchQuery, showTrash } = this.props;
const { openedTag, searchQuery, searchSelected, showTrash } = this.props;
const hasQuery = searchQuery.length > 0;
const placeholder = showTrash ? 'Trash' : openedTag?.name ?? 'All Notes';
const shouldShowSuggestions = hasQuery && !searchSelected;

const screenReaderLabel =
'Search ' + (openedTag ? 'notes with tag ' : '') + placeholder;

return (
<div className="search-field">
<input
aria-label={screenReaderLabel}
ref={this.inputField}
type="search"
placeholder={placeholder}
onChange={this.update}
onKeyUp={this.interceptEsc}
value={searchQuery}
spellCheck={false}
/>
<button
aria-label="Clear search"
hidden={!hasQuery}
onClick={this.clearQuery}
>
<SmallCrossIcon />
</button>
</div>
<Fragment>
<div className="search-field">
<input
aria-label={screenReaderLabel}
ref={this.inputField}
type="search"
placeholder={placeholder}
onChange={this.update}
onKeyUp={this.interceptEsc}
value={searchQuery}
spellCheck={false}
/>
<button
aria-label="Clear search"
hidden={!hasQuery}
onClick={this.clearQuery}
>
<SmallCrossIcon />
</button>
</div>
{shouldShowSuggestions && (
<SearchSuggestions
query={searchQuery}
onSearch={this.doSearch}
// storeKeyHandler={this.storeKeyHandler}
/>
)}
</Fragment>
);
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/search-field/style.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.search-field {
display: flex;
position: relative;
flex-direction: row;
align-items: center;
width: 100%;
Expand Down
137 changes: 137 additions & 0 deletions lib/search-suggestions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { Component } from 'react';
// import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { State } from '../state';
import { search } from '../state/ui/actions';
import SmallSearchIcon from '../icons/search-small';

import type * as S from '../state';
// import type * as T from '../types';

type StateProps = {
searchQuery: string;
searchHistory: [string];
// storeKeyHandler: () => any;
};

type DispatchProps = {
onSearch: (query: string) => any;
};

type Props = StateProps & DispatchProps;

export class SearchSuggestions extends Component<Props> {
static displayName = 'SearchSuggestions';

// state = {
// selectedItem: 0,
// };

// componentDidMount() {
// this.props.storeKeyHandler({
// next: this.nextItem,
// prev: this.prevItem,
// select: this.selectItem,
// });
// }

// componentDidUpdate() {
// scroll selected item into view
// const selectedItem = this.refs.selectedItem;
// const domNode = null;
// todo scroll only when needed (i.e. item is not visible)
// if (selectedItem) {
// domNode = ReactDOM.findDOMNode(selectedItem);
// domNode?.scrollIntoView(false);
// }

// this.props.storeKeyHandler({
// next: this.nextItem,
// prev: this.prevItem,
// select: this.selectItem,
// });
// }

// nextItem = () => {
// const { searchHistory } = this.props;
// const { selectedItem } = this.state;
// const newItem = Math.min(filteredTags.length, selectedItem + 1);
// this.setState({ selectedItem: newItem });
// };

// prevItem = () => {
// const { selectedItem } = this.state;
// const newItem = Math.max(0, selectedItem - 1);
// this.setState({ selectedItem: newItem });
// };

// selectItem = () => {
// const { selectedItem } = this.state;
// const { query, onSearch, filteredTags } = this.props;
// if (selectedItem === 0) {
// onSearch(query);
// } else {
// onSearch(`tag:${filteredTags[selectedItem - 1].id}`);
// }
// };

render() {
const { onSearch, searchQuery, searchHistory } = this.props;
// const { selectedItem } = this.state;
const screenReaderLabel = 'Search suggestions';

return (
<div className="search-suggestions" aria-label={screenReaderLabel}>
<ul className="search-suggestions-list">
<li
id="query"
className={'search-suggestion-row'
// selectedItem === 0
// ? 'search-suggestion-row search-suggestion-row-selected'
// : 'search-suggestion-row'
}
onClick={() => onSearch(searchQuery)}
>
<SmallSearchIcon />
<span className="search-suggestion">
{decodeURIComponent(searchQuery)}
</span>
</li>
{searchHistory.length > 0 &&
searchHistory.map((query, index) => (
<li
key={query}
id={query}
className={'search-suggestion-row'
// selectedItem === index + 1
// ? 'search-suggestion-row search-suggestion-row-selected'
// : 'search-suggestion-row'
}
onClick={() => onSearch(query)}
// ref={selectedItem === index + 1 ? 'selectedItem' : ''}
>
<span className="search-suggestion">
{decodeURIComponent(query)}
</span>
</li>
))}
</ul>
</div>
);
}
}

const mapStateToProps: S.MapState<StateProps> = ({
ui: { searchQuery },
}: State) => ({
searchQuery,
searchHistory: ["test","meow"]
});

const mapDispatchToProps: S.MapDispatch<DispatchProps> = (dispatch) => ({
onSearch: (query: string) => {
dispatch(search(query));
},
});

export default connect(mapStateToProps, mapDispatchToProps)(SearchSuggestions);
85 changes: 85 additions & 0 deletions lib/search-suggestions/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
.search-suggestions {
position: absolute;
top: 48px;
left: 56px;
z-index: 10;

background: $studio-white;
display: inline-block;
padding: none;
min-width: 288px;
border-radius: 4px;
overflow: scroll;
max-height: 226px;
box-shadow: 0 3px 6px 0 rgba(0,0,0,0.15);
text-align: left;

.search-suggestions-list {
list-style-type: none;
padding: 8px 0;
margin: 0;
line-height: 40px;
}

.search-suggestion-row {
cursor: pointer;
position: relative;
padding: 0 16px;
}
.search-suggestion {
overflow-wrap: anywhere;
margin-left: 40px;
}

.icon-search-small {
display: inline-block;
position: absolute;
transition: $anim-transition;
top: 8px;
height: 24px;
width: 24px;

margin-right: 16px;
padding: 0;
}

}

.theme-light {
.search-suggestions {
background: $studio-white;
}

.search-suggestion-row {
.icon-search-small {
fill: $studio-gray-50;
}
&:hover {
background: $studio-gray-5;
}
}

.search-suggestion-row-selected {
background-color: $studio-blue-5;
}
}

.theme-dark {
.search-suggestions {
background: $studio-gray-90;
color: $studio-white;
}

.search-suggestion-row {
.icon-search-small {
fill: $studio-gray-30;
}
&:hover {
background: $studio-blue-70;
}
}

.search-suggestion-row-selected {
background-color: $studio-gray-70;
}
}
1 change: 1 addition & 0 deletions scss/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@import 'revision-selector/style';
@import 'search-bar/style';
@import 'search-field/style';
@import 'search-suggestions/style';
@import 'tag-email-tooltip/style';
@import 'tag-field/style';
@import 'tag-input/style';
Expand Down

0 comments on commit 3e18a4b

Please sign in to comment.