Commit 05db2def authored by Eugen Rochko's avatar Eugen Rochko

Add search to the instance picker

parent 5c0a0ac0
......@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"axios": "^0.16.2",
"fuzzysearch": "^1.0.3",
"gh-pages": "^0.12.0",
"react": "^15.5.3",
"react-custom-scrollbars": "^4.1.2",
......
import React from 'react';
import { fetchInstances } from './actions';
import WizardRow from './WizardRow';
import { Scrollbars } from 'react-custom-scrollbars';
export default class Wizard extends React.Component {
export default class Wizard extends React.PureComponent {
componentDidMount () {
this.props.dispatch(fetchInstances());
this.props.onMount();
}
handleChange = e => {
this.props.onChange(e.target.value);
}
handleClear = e => {
e.preventDefault();
this.props.onClear();
}
render () {
const { instances } = this.props;
const { instances, searchValue } = this.props;
const hasValue = searchValue.length > 0;
return (
<div className='wizard-page' id='getting-started'>
......@@ -31,6 +40,23 @@ export default class Wizard extends React.Component {
)}
</Scrollbars>
</div>
<div className='wizard-controls'>
<div className='search'>
<input
className='search__input'
type='text'
placeholder='Search'
value={searchValue}
onChange={this.handleChange}
/>
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
<i className={`ion-android-search ${hasValue ? '' : 'active'}`} />
<i className={`ion-android-cancel ${hasValue ? 'active' : ''}`} />
</div>
</div>
</div>
</div>
);
}
......
import Wizard from './Wizard';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchInstances, changeSearchValue } from './actions';
import fuzzysearch from 'fuzzysearch';
const getInstances = createSelector(
[state => state.instances],
instances => instances.filter(item => !item.dead && item.uptime > 0.70)
[
state => state.searchValue,
state => state.instances,
],
(searchValue, instances) => {
searchValue = searchValue.toLowerCase();
return instances.filter(item => {
const eligible = !item.dead && item.uptime > 0.70;
const isSearching = searchValue.length > 0;
return eligible &&
(!isSearching || fuzzysearch(searchValue, item.searchable));
});
}
);
const mapStateToProps = state => ({
instances: getInstances(state),
searchValue: state.searchValue,
});
const mapDispatchToProps = dispatch => ({
onMount: () => dispatch(fetchInstances()),
onChange: value => dispatch(changeSearchValue(value)),
onClear: () => dispatch(changeSearchValue('')),
});
export default connect(mapStateToProps)(Wizard);
export default connect(mapStateToProps, mapDispatchToProps)(Wizard);
import axios from 'axios';
export const INSTANCES_FETCH_SUCCESS = 'INSTANCES_FETCH_SUCCESS';
export const SEARCH_VALUE_CHANGE = 'SEARCH_VALUE_CHANGE';
export function fetchInstances() {
return (dispatch, getState) => {
......@@ -19,3 +20,10 @@ export function fetchInstancesSuccess(data) {
data,
};
};
export function changeSearchValue(data) {
return {
type: SEARCH_VALUE_CHANGE,
data,
};
};
import { INSTANCES_FETCH_SUCCESS } from './actions';
import {
INSTANCES_FETCH_SUCCESS,
SEARCH_VALUE_CHANGE,
} from './actions';
const initialState = {
instances: [],
searchValue: '',
};
const createSearchable = item => {
let searchable = [];
searchable.push(item.name);
if (item.info) {
searchable.push(item.info.theme || 'general');
}
return { ...item, searchable: searchable.join(' ').toLowerCase() };
};
export default function reducer(state = initialState, action) {
switch(action.type) {
case INSTANCES_FETCH_SUCCESS:
return { ...state, instances: action.data };
return { ...state, instances: action.data.map(createSearchable) };
case SEARCH_VALUE_CHANGE:
return { ...state, searchValue: action.data };
default:
return state;
}
......
......@@ -489,6 +489,10 @@ $phi: 1.6180339887498948482;
height: 120px;
}
}
.as-seen-on .logo-grid {
flex-direction: column;
}
}
@keyframes floating {
......
......@@ -21,10 +21,12 @@
.wizard {
margin: 50px auto;
margin-bottom: 15px;
border: 1px solid darken($darkest, 4%);
background: lighten($darkest, 8%);
border-radius: 10px;
max-width: 800px;
overflow: hidden;
.wizard-header {
display: flex;
......@@ -132,3 +134,94 @@
background: $error;
}
}
.search {
position: relative;
}
.wizard-controls {
margin-bottom: 50px;
display: flex;
justify-content: flex-end;
}
.search__input {
padding-right: 30px;
outline: 0;
box-sizing: border-box;
border-radius: 4px;
display: block;
width: 100%;
border: none;
padding: 10px;
padding-right: 30px;
font-family: inherit;
background: darken($darkest, 8%);
color: $lighter;
font-size: 14px;
margin: 0;
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus,
&:active {
outline: 0 !important;
}
&:focus {
background: darken($darkest, 4%);
}
@media screen and (max-width: 600px) {
font-size: 16px;
}
}
.search__icon {
.ion-android-search, .ion-android-cancel {
position: absolute;
top: 10px;
right: 10px;
z-index: 2;
display: inline-block;
opacity: 0;
transition: all 100ms linear;
font-size: 18px;
width: 18px;
height: 18px;
line-height: 18px;
color: $lighter;
cursor: default;
pointer-events: none;
&.active {
pointer-events: auto;
opacity: 0.3;
}
}
.ion-android-search {
transform: translateZ(0) rotate(90deg);
&.active {
pointer-events: none;
transform: translateZ(0) rotate(0deg);
}
}
.ion-android-cancel {
transform: translateZ(0) rotate(0deg);
cursor: pointer;
&.active {
transform: translateZ(0) rotate(90deg);
}
&:hover {
color: $lightest;
}
}
}
......@@ -2337,6 +2337,10 @@ function-bind@^1.0.2, function-bind@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
fuzzysearch@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
gauge@~2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09"
......@@ -4423,14 +4427,14 @@ promise@7.1.1, promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.5.10:
prop-types@^15.5.10, prop-types@~15.5.0:
version "15.5.10"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
prop-types@^15.5.2, prop-types@~15.5.0:
prop-types@^15.5.2:
version "15.5.4"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.4.tgz#2ed3692716a5060f8cc020946d8238e7419d92c0"
dependencies:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment