mirror of
https://github.com/dap-ps/discover.git
synced 2025-03-03 18:30:32 +00:00
Add search box
This commit is contained in:
parent
0a1db006c5
commit
e6e63f3b2a
3
src/common/assets/images/search.svg
Normal file
3
src/common/assets/images/search.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.92798 11.856C2.65405 11.856 0 9.2019 0 5.92798C0 2.65405 2.65405 0 5.92798 0C9.2019 0 11.856 2.65405 11.856 5.92798C11.856 7.20825 11.45 8.39368 10.76 9.36267L15.7087 14.3114C16.0945 14.6973 16.0945 15.3228 15.7087 15.7086C15.3228 16.0945 14.6973 16.0945 14.3115 15.7086L9.36279 10.76C8.3938 11.4501 7.20825 11.856 5.92798 11.856ZM5.92798 9.87988C8.1106 9.87988 9.88013 8.1106 9.88013 5.92798C9.88013 3.74536 8.1106 1.97595 5.92798 1.97595C3.74536 1.97595 1.97607 3.74536 1.97607 5.92798C1.97607 8.1106 3.74536 9.87988 5.92798 9.87988Z" fill="#939BA1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 709 B |
8
src/common/components/Search/Search.container.js
Normal file
8
src/common/components/Search/Search.container.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { connect } from 'react-redux'
|
||||
import Search from './Search'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
dappState: state.dapps,
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps)(Search)
|
67
src/common/components/Search/Search.jsx
Normal file
67
src/common/components/Search/Search.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import searchIcon from '../../assets/images/search.svg'
|
||||
import SearchResultItem from './SearchResultList'
|
||||
import { DappState } from '../../data/dapp'
|
||||
import styles from './Search.module.scss'
|
||||
|
||||
class Search extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
dapps: [],
|
||||
isSearching: false,
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const { value } = e.target
|
||||
const { dappState } = this.props
|
||||
if (value.length > 1) {
|
||||
const dapps = dappState.dapps.filter(dapp => dapp.name.startsWith(value))
|
||||
this.setState({ dapps, isSearching: true })
|
||||
} else if (value === '') {
|
||||
this.setState({
|
||||
isSearching: false,
|
||||
dapps: [],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { searchStyle, onSearch, searchResultStyle } = this.props
|
||||
const { dapps, isSearching } = this.state
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<img src={searchIcon} alt="Search Icon" width="16" height="16" />
|
||||
<input
|
||||
type="text"
|
||||
onChange={e => this.handleChange(e)}
|
||||
className={[styles.search, searchStyle].join(' ')}
|
||||
placeholder="Search Dapps"
|
||||
onSearch={onSearch(isSearching)}
|
||||
/>
|
||||
</div>
|
||||
<div className={[styles.searchResults, searchResultStyle].join(' ')}>
|
||||
<SearchResultItem showActionButtons={false} dapps={dapps} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Search.propTypes = {
|
||||
searchStyle: PropTypes.string,
|
||||
searchResultStyle: PropTypes.string,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
Search.defaultProps = {
|
||||
searchStyle: null,
|
||||
searchResultStyle: null,
|
||||
}
|
||||
|
||||
export default Search
|
29
src/common/components/Search/Search.module.scss
Normal file
29
src/common/components/Search/Search.module.scss
Normal file
@ -0,0 +1,29 @@
|
||||
@import '../../styles/_variables.scss';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
border: 1px solid #eef2f5;
|
||||
border-radius: calculateRem(4);
|
||||
overflow: hidden;
|
||||
margin: calculateRem(16);
|
||||
img {
|
||||
padding: calculateRem(5);
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
font-family: 'Inter';
|
||||
font-size: calculateRem(15);
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
&::placeholder {
|
||||
color: #939ba1;
|
||||
}
|
||||
}
|
||||
|
||||
.searchResults {
|
||||
z-index: 99;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactImageFallback from 'react-image-fallback'
|
||||
import { DappModel } from '../../../utils/models'
|
||||
import styles from './SearchResultItem.module.scss'
|
||||
import icon from '../../../assets/images/icon.svg'
|
||||
|
||||
const SearchResultItem = props => {
|
||||
const { dapp } = props
|
||||
|
||||
const { name, description, image, url } = dapp
|
||||
|
||||
return (
|
||||
<div className={`${styles.container}`}>
|
||||
<div className={styles.imgWrapper}>
|
||||
<ReactImageFallback
|
||||
className={styles.image}
|
||||
src={image}
|
||||
fallbackImage={icon}
|
||||
alt="App icon"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.column}>
|
||||
<>
|
||||
<h2 className={styles.header}>{name}</h2>
|
||||
<p
|
||||
className={styles.description}
|
||||
style={{ WebkitBoxOrient: 'vertical' }}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
<a className={styles.link} href={{ url }}>
|
||||
{url}
|
||||
→
|
||||
</a>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SearchResultItem.defaultProps = {
|
||||
visible: true,
|
||||
}
|
||||
|
||||
SearchResultItem.propTypes = {
|
||||
dapp: PropTypes.shape(DappModel).isRequired,
|
||||
visible: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default SearchResultItem
|
@ -0,0 +1,59 @@
|
||||
@import '../../../styles/variables';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
margin: calculateRem(10);
|
||||
font-family: 'Inter';
|
||||
}
|
||||
|
||||
.header {
|
||||
color: $headline-color !important;
|
||||
font-size: calculateRem(15);
|
||||
line-height: calculateRem(22);
|
||||
margin-bottom: calculateRem(2);
|
||||
margin-top: calculateRem(12);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #939ba1 !important;
|
||||
font-size: calculateRem(13);
|
||||
}
|
||||
|
||||
.column {
|
||||
align-self: flex-end;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: calculateRem(40);
|
||||
max-height: calculateRem(40);
|
||||
margin-top: calculateRem(15);
|
||||
margin-right: calculateRem(16);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-top: calculateRem(2);
|
||||
font-size: calculateRem(12);
|
||||
}
|
||||
|
||||
.imgWrapper {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (max-width: $desktop) {
|
||||
.column {
|
||||
width: 78vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
.column {
|
||||
width: 24vw;
|
||||
}
|
||||
}
|
3
src/common/components/Search/SearchResultItem/index.js
Normal file
3
src/common/components/Search/SearchResultItem/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import SearchResultItem from './SearchResultItem'
|
||||
|
||||
export default SearchResultItem
|
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DappListModel } from '../../../utils/models'
|
||||
import SearchResultItem from '../SearchResultItem'
|
||||
|
||||
const SearchResultList = ({ dapps }) =>
|
||||
dapps &&
|
||||
dapps.map((dapp, i) => (
|
||||
<SearchResultItem dapp={dapp} key={dapp.id} position={i + 1} />
|
||||
))
|
||||
|
||||
SearchResultList.propTypes = {
|
||||
dapps: DappListModel.isRequired,
|
||||
}
|
||||
|
||||
export default SearchResultList
|
3
src/common/components/Search/SearchResultList/index.js
Normal file
3
src/common/components/Search/SearchResultList/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import SearchResultList from './SearchResultList'
|
||||
|
||||
export default SearchResultList
|
3
src/common/components/Search/index.js
Normal file
3
src/common/components/Search/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Search from './Search.container'
|
||||
|
||||
export default Search
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CategoryIcon from '../../common/components/CategoryIcon'
|
||||
import ViewAll from '../../common/components/ViewAll'
|
||||
import Search from '../../common/components/Search'
|
||||
import categories from '../../common/utils/categories'
|
||||
import humanise from '../../common/utils/humanise'
|
||||
import dropdownArrows from '../../common/assets/images/dropdown-arrows.svg'
|
||||
@ -10,13 +11,14 @@ import styles from './CategorySelector.module.scss'
|
||||
class CategorySelector extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { open: false }
|
||||
this.state = { open: false, isSearching: false }
|
||||
this.toggle = this.toggle.bind(this)
|
||||
this.updateCategory = this.updateCategory.bind(this)
|
||||
this.container = React.createRef()
|
||||
this.onClickSubmit = this.onClickSubmit.bind(this)
|
||||
this.onClickHighestRanked = this.onClickHighestRanked.bind(this)
|
||||
this.onClickRecentlyAdded = this.onClickRecentlyAdded.bind(this)
|
||||
this.onSearch = this.onSearch.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -43,6 +45,10 @@ class CategorySelector extends React.Component {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
|
||||
onSearch(isSearching) {
|
||||
this.setState({ isSearching })
|
||||
}
|
||||
|
||||
onClickHighestRanked(e) {
|
||||
const { onClickCloseDesktopMenu } = this.props
|
||||
onClickCloseDesktopMenu()
|
||||
@ -77,6 +83,7 @@ class CategorySelector extends React.Component {
|
||||
showSubmitDApp,
|
||||
} = this.props
|
||||
let { open } = this.state
|
||||
const { isSearching } = this.state
|
||||
if (alwaysOpen === true) open = true
|
||||
|
||||
return (
|
||||
@ -85,29 +92,45 @@ class CategorySelector extends React.Component {
|
||||
style={open ? { visible: 'block' } : { display: 'none' }}
|
||||
className={styles.open}
|
||||
>
|
||||
<div className={styles.openHeader}>
|
||||
<Search
|
||||
searchStyle={styles.search}
|
||||
onSearch={this.onSearch}
|
||||
searchResultStyle={styles.searchResult}
|
||||
/>
|
||||
<div
|
||||
className={styles.openHeader}
|
||||
style={isSearching ? { display: 'none' } : { display: 'flex' }}
|
||||
>
|
||||
<h2>Categories</h2>
|
||||
<ViewAll size="small" />
|
||||
</div>
|
||||
{categories.map(c => (
|
||||
<button
|
||||
className={
|
||||
c.key === category
|
||||
? [styles.openButton, styles.selected].join(' ')
|
||||
: styles.openButton
|
||||
}
|
||||
key={c.key}
|
||||
type="button"
|
||||
value={c.key}
|
||||
onClick={this.updateCategory}
|
||||
>
|
||||
<CategoryIcon category={c.key} />
|
||||
{c.value}
|
||||
</button>
|
||||
))}
|
||||
<div
|
||||
className={styles.categories}
|
||||
style={isSearching ? { display: 'none' } : { display: 'block' }}
|
||||
>
|
||||
{categories.map(c => (
|
||||
<button
|
||||
className={
|
||||
c.key === category
|
||||
? [styles.openButton, styles.selected].join(' ')
|
||||
: styles.openButton
|
||||
}
|
||||
key={c.key}
|
||||
type="button"
|
||||
value={c.key}
|
||||
onClick={this.updateCategory}
|
||||
>
|
||||
<CategoryIcon category={c.key} />
|
||||
{c.value}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{showLists && (
|
||||
<>
|
||||
<div
|
||||
className={styles.categories}
|
||||
style={isSearching ? { display: 'none' } : { display: 'block' }}
|
||||
>
|
||||
<div className={`${styles.openHeader} ${styles.spacing}`}>
|
||||
<h2>Lists</h2>
|
||||
</div>
|
||||
@ -151,7 +174,7 @@ class CategorySelector extends React.Component {
|
||||
</svg>
|
||||
{'Recently added'}
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSubmitDApp && (
|
||||
@ -159,6 +182,7 @@ class CategorySelector extends React.Component {
|
||||
className={`${styles.openButton} ${styles.submitDapp}`}
|
||||
type="button"
|
||||
onClick={this.onClickSubmit}
|
||||
style={isSearching ? { display: 'none' } : { display: 'flex' }}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
|
@ -1,5 +1,18 @@
|
||||
@import '../../common/styles/variables';
|
||||
|
||||
.search {
|
||||
width: 85%;
|
||||
margin: calculateRem(9);
|
||||
}
|
||||
|
||||
.searchResult {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.categories {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.open {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08),
|
||||
|
@ -4,6 +4,7 @@ import RecentlyAdded from '../RecentlyAdded'
|
||||
import HighestRanked from '../HighestRanked'
|
||||
import Categories from '../Categories'
|
||||
import FeaturedDapps from '../../common/components/FeatureDapps'
|
||||
import Search from '../../common/components/Search'
|
||||
import Footer from '../Footer'
|
||||
import LoadingHome from '../LoadingHome'
|
||||
import featured from '../../common/data/featured'
|
||||
@ -13,11 +14,19 @@ import DesktopMenu from '../DesktopMenu/DesktopMenu.container'
|
||||
class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
this.state = {
|
||||
isSearching: false,
|
||||
}
|
||||
this.onSearch = this.onSearch.bind(this)
|
||||
}
|
||||
|
||||
onSearch(isSearching) {
|
||||
this.setState({ isSearching })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dapps } = this.props
|
||||
const { isSearching } = this.state
|
||||
const loaded = dapps.loaded
|
||||
|
||||
return (
|
||||
@ -28,6 +37,13 @@ class Home extends React.Component {
|
||||
<h2 className={styles.headline}>Discover</h2>
|
||||
</div>
|
||||
<DesktopMenu />
|
||||
<div className={styles.mobileSearch}>
|
||||
<Search
|
||||
searchStyle={styles.search}
|
||||
searchResultStyle={styles.searchResult}
|
||||
onSearch={this.onSearch}
|
||||
/>
|
||||
</div>
|
||||
<FeaturedDapps featured={featured} />
|
||||
<Categories />
|
||||
<HighestRanked />
|
||||
|
@ -9,3 +9,25 @@
|
||||
font-size: calculateRem(17);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mobileSearch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
padding: calculateRem(9);
|
||||
}
|
||||
|
||||
@media (max-width: $desktop) {
|
||||
.mobileSearch {
|
||||
display: block;
|
||||
}
|
||||
.searchResult {
|
||||
width: 98vw;
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user