2019-08-03 00:45:13 +09:00
|
|
|
import React, { useState, useEffect, useRef } from 'react'
|
2019-08-01 02:01:20 +09:00
|
|
|
import styled, { createGlobalStyle } from 'styled-components'
|
2019-08-03 13:36:29 +09:00
|
|
|
import { Helmet } from 'react-helmet'
|
|
|
|
import { useTranslation } from 'react-i18next'
|
2019-07-31 03:43:13 +09:00
|
|
|
|
2019-07-31 12:22:31 +09:00
|
|
|
import DomainCard from './components/cards/DomainCard'
|
2019-08-06 00:45:18 +09:00
|
|
|
import GithubCard from './components/cards/GithubCard'
|
2019-07-31 12:22:31 +09:00
|
|
|
import NpmCard from './components/cards/NpmCard'
|
|
|
|
import PypiCard from './components/cards/PypiCard'
|
2019-08-02 16:27:05 +09:00
|
|
|
import RubyGemsCard from './components/cards/RubyGemsCard'
|
2019-08-06 00:45:18 +09:00
|
|
|
import CratesioCard from './components/cards/CratesioCard'
|
|
|
|
import HomebrewCard from './components/cards/HomebrewCard'
|
2019-08-05 22:59:47 +09:00
|
|
|
import LinuxCard from './components/cards/LinuxCard'
|
2019-08-06 00:45:18 +09:00
|
|
|
import TwitterCard from './components/cards/TwitterCard'
|
|
|
|
import SlackCard from './components/cards/SlackCard'
|
|
|
|
import S3Card from './components/cards/S3Card'
|
|
|
|
import JsOrgCard from './components/cards/JsOrgCard'
|
2019-08-07 14:08:32 +09:00
|
|
|
import GithubSearchCard from './components/cards/GithubSearchCard'
|
|
|
|
import AppStoreCard from './components/cards/AppStoreCard'
|
2019-08-03 21:51:12 +09:00
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
import Welcome from './components/Welcome'
|
|
|
|
import Footer from './components/Footer'
|
|
|
|
import Suggestion from './components/Suggestion'
|
2019-07-27 19:18:54 +09:00
|
|
|
|
2019-08-03 00:35:47 +09:00
|
|
|
import { useDeferredState } from './hooks/state'
|
|
|
|
import { mobile } from './util/css'
|
|
|
|
import { isStandalone } from './util/pwa'
|
|
|
|
|
2019-07-30 23:27:28 +09:00
|
|
|
export default function App() {
|
2019-08-06 02:07:05 +09:00
|
|
|
const [query, setQuery] = useDeferredState(1000, '')
|
2019-08-03 00:45:13 +09:00
|
|
|
const [inputValue, setInputValue] = useState('')
|
2019-08-03 16:44:48 +09:00
|
|
|
const [suggested, setSuggested] = useState(false)
|
2019-08-06 02:07:05 +09:00
|
|
|
const inputRef = useRef()
|
2019-08-03 13:36:29 +09:00
|
|
|
const { t } = useTranslation()
|
2019-07-27 19:18:54 +09:00
|
|
|
|
2019-08-03 00:45:13 +09:00
|
|
|
const queryGiven = query && query.length > 0
|
|
|
|
|
|
|
|
useEffect(() => {
|
2019-08-03 21:51:12 +09:00
|
|
|
const modifiedValue = inputValue.replace(/[\s@+!#$%^&*()[\]]/g, '')
|
2019-08-03 17:34:02 +09:00
|
|
|
setQuery(modifiedValue)
|
2019-08-03 00:45:13 +09:00
|
|
|
}, [inputValue, setQuery])
|
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
useEffect(() => {
|
|
|
|
if (query.length === 0) {
|
|
|
|
setSuggested(false)
|
|
|
|
}
|
|
|
|
}, [query])
|
|
|
|
|
|
|
|
// set input value
|
2019-08-03 00:45:13 +09:00
|
|
|
function onInputChange(e) {
|
2019-08-03 16:44:48 +09:00
|
|
|
const value = e.target.value
|
|
|
|
setInputValue(value)
|
2019-07-27 19:18:54 +09:00
|
|
|
}
|
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
// clear input form and focus on it
|
2019-08-03 00:45:13 +09:00
|
|
|
function onLogoClick(e) {
|
|
|
|
setInputValue('')
|
|
|
|
inputRef.current.focus()
|
|
|
|
}
|
2019-08-01 02:22:20 +09:00
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
// invoke when user clicked one of the suggested items
|
|
|
|
function onSuggestionCompleted(name) {
|
|
|
|
setInputValue(name)
|
|
|
|
setSuggested(true)
|
|
|
|
}
|
|
|
|
|
2019-07-27 19:18:54 +09:00
|
|
|
return (
|
2019-08-01 02:01:20 +09:00
|
|
|
<>
|
|
|
|
<GlobalStyle />
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-08-03 13:36:29 +09:00
|
|
|
<Helmet>
|
|
|
|
<title>namaæ — {t('title')}</title>
|
|
|
|
</Helmet>
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-08-02 04:23:21 +09:00
|
|
|
<Header>
|
|
|
|
<InputContainer>
|
2019-08-03 00:45:13 +09:00
|
|
|
<Logo onClick={onLogoClick}>namæ</Logo>
|
2019-08-03 16:44:48 +09:00
|
|
|
<InputView
|
2019-08-03 13:36:29 +09:00
|
|
|
onChange={onInputChange}
|
|
|
|
value={inputValue}
|
|
|
|
ref={inputRef}
|
|
|
|
placeholder={t('placeholder')}
|
2019-08-08 21:53:09 +09:00
|
|
|
aria-label="search query"
|
2019-08-03 13:36:29 +09:00
|
|
|
/>
|
2019-08-03 16:44:48 +09:00
|
|
|
{queryGiven && !suggested ? (
|
|
|
|
<Suggestion onSubmit={onSuggestionCompleted} query={query} />
|
|
|
|
) : null}
|
2019-08-02 04:23:21 +09:00
|
|
|
</InputContainer>
|
|
|
|
</Header>
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-08-01 02:01:20 +09:00
|
|
|
<Content>
|
2019-08-01 13:21:23 +09:00
|
|
|
{queryGiven ? (
|
2019-08-05 22:59:39 +09:00
|
|
|
<SearchResult>
|
|
|
|
<Cards>
|
2019-08-06 02:07:05 +09:00
|
|
|
<DomainCard query={query} />
|
|
|
|
<GithubCard query={query} />
|
|
|
|
<NpmCard query={query} />
|
|
|
|
<PypiCard query={query} />
|
|
|
|
<RubyGemsCard query={query} />
|
|
|
|
<CratesioCard query={query} />
|
|
|
|
<HomebrewCard query={query} />
|
|
|
|
<LinuxCard query={query} />
|
|
|
|
<TwitterCard query={query} />
|
|
|
|
<SlackCard query={query} />
|
|
|
|
<S3Card query={query} />
|
|
|
|
<JsOrgCard query={query} />
|
2019-08-07 14:08:32 +09:00
|
|
|
</Cards>
|
|
|
|
<Cards>
|
2019-08-06 00:45:18 +09:00
|
|
|
<GithubSearchCard query={query} />
|
2019-08-06 01:17:29 +09:00
|
|
|
<AppStoreCard query={query} />
|
2019-08-05 22:59:39 +09:00
|
|
|
</Cards>
|
|
|
|
</SearchResult>
|
2019-08-07 17:42:07 +09:00
|
|
|
) : (
|
|
|
|
!isStandalone() && <Welcome />
|
|
|
|
)}
|
2019-08-01 02:01:20 +09:00
|
|
|
</Content>
|
2019-08-07 17:42:07 +09:00
|
|
|
|
|
|
|
<Footer />
|
2019-08-01 02:01:20 +09:00
|
|
|
</>
|
2019-07-27 19:18:54 +09:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-08-01 02:01:20 +09:00
|
|
|
const GlobalStyle = createGlobalStyle`
|
2019-08-01 13:21:23 +09:00
|
|
|
* {
|
|
|
|
box-sizing: border-box;
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
html {
|
|
|
|
font-size: 16px;
|
|
|
|
}
|
|
|
|
|
2019-08-01 02:01:20 +09:00
|
|
|
body {
|
2019-08-01 13:21:23 +09:00
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
|
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
|
|
sans-serif;
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
-moz-osx-font-smoothing: grayscale;
|
2019-08-02 04:23:21 +09:00
|
|
|
background: #ffffff;
|
|
|
|
${mobile} {
|
|
|
|
background: #f5f5f5;
|
|
|
|
}
|
2019-08-01 02:01:20 +09:00
|
|
|
}
|
|
|
|
`
|
|
|
|
|
2019-08-02 04:23:21 +09:00
|
|
|
const Content = styled.div`
|
2019-08-02 16:27:05 +09:00
|
|
|
padding-top: 100px;
|
2019-08-02 04:23:21 +09:00
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
padding-top: 60px;
|
|
|
|
}
|
|
|
|
`
|
2019-08-01 01:48:55 +09:00
|
|
|
|
2019-07-31 03:43:13 +09:00
|
|
|
const Header = styled.header`
|
2019-08-01 02:30:35 +09:00
|
|
|
padding: 0 40px;
|
2019-08-01 01:48:55 +09:00
|
|
|
background-image: linear-gradient(180deg, #a2d4ff 0%, #ac57ff 99%);
|
|
|
|
|
|
|
|
${mobile} {
|
2019-08-01 02:30:35 +09:00
|
|
|
padding: 0 20px;
|
2019-08-01 01:48:55 +09:00
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
const InputContainer = styled.div`
|
|
|
|
transform: translateY(40px);
|
|
|
|
padding: 20px;
|
|
|
|
background: #ffffff;
|
2019-08-01 13:21:23 +09:00
|
|
|
box-shadow: 0 10px 20px 0 #c7dcf7;
|
|
|
|
border-radius: 20px;
|
2019-08-01 01:48:55 +09:00
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
transform: translateY(20px);
|
|
|
|
}
|
2019-07-31 00:26:49 +09:00
|
|
|
`
|
|
|
|
|
2019-07-31 03:43:13 +09:00
|
|
|
const Logo = styled.div`
|
2019-07-31 10:36:54 +09:00
|
|
|
margin-bottom: 5px;
|
2019-08-01 01:48:55 +09:00
|
|
|
text-align: center;
|
2019-07-31 03:43:13 +09:00
|
|
|
font-family: sans-serif;
|
|
|
|
font-weight: bold;
|
2019-08-01 01:48:55 +09:00
|
|
|
font-size: 20px;
|
|
|
|
color: #4a90e2;
|
2019-08-03 00:45:13 +09:00
|
|
|
cursor: pointer;
|
2019-08-01 02:01:20 +09:00
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
font-size: 15px;
|
|
|
|
}
|
2019-07-31 03:43:13 +09:00
|
|
|
`
|
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
const InputView = styled.input.attrs({
|
2019-07-31 12:22:31 +09:00
|
|
|
type: 'text',
|
|
|
|
autocomplete: 'off',
|
|
|
|
autocorrect: 'off',
|
|
|
|
autocapitalize: 'off',
|
|
|
|
spellcheck: 'false',
|
|
|
|
})`
|
2019-07-30 23:27:28 +09:00
|
|
|
width: 100%;
|
2019-08-01 13:21:23 +09:00
|
|
|
border: none;
|
2019-07-30 23:27:28 +09:00
|
|
|
outline: none;
|
2019-07-31 03:52:17 +09:00
|
|
|
text-align: center;
|
2019-07-30 23:27:28 +09:00
|
|
|
font-family: monospace;
|
2019-08-01 13:21:23 +09:00
|
|
|
font-size: 5rem;
|
2019-08-03 13:36:29 +09:00
|
|
|
line-height: 1.2em;
|
2019-07-31 03:43:13 +09:00
|
|
|
|
2019-07-31 12:22:31 +09:00
|
|
|
${mobile} {
|
2019-08-01 14:00:22 +09:00
|
|
|
font-size: 2rem;
|
2019-07-31 12:22:31 +09:00
|
|
|
}
|
2019-07-31 03:43:13 +09:00
|
|
|
`
|
2019-08-05 22:59:39 +09:00
|
|
|
|
|
|
|
const SearchResult = styled.div``
|
|
|
|
|
|
|
|
const Cards = styled.div`
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
justify-content: center;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
`
|