1
0
mirror of https://github.com/uetchy/namae.git synced 2025-07-02 14:20:03 +09:00
namae/web/src/App.tsx

233 lines
5.7 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect, useRef } from 'react'
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-08-27 03:15:35 +09:00
import DomainCard from './components/cards/domains'
import GithubCard from './components/cards/github-repository'
import NpmCard from './components/cards/npm'
import PypiCard from './components/cards/pypi'
import RubyGemsCard from './components/cards/rubygems'
import CratesioCard from './components/cards/cratesio'
import HomebrewCard from './components/cards/homebrew'
import LinuxCard from './components/cards/linux'
import TwitterCard from './components/cards/twitter'
import SpectrumCard from './components/cards/spectrum'
import SlackCard from './components/cards/slack'
import S3Card from './components/cards/s3'
import JsOrgCard from './components/cards/jsorg'
import GithubSearchCard from './components/cards/github-search'
import AppStoreCard from './components/cards/appstore'
import HerokuCard from './components/cards/heroku'
import NowCard from './components/cards/now'
2019-09-01 00:00:24 +09:00
import NtaCard from './components/cards/nta'
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-14 20:00:31 +09:00
import { useDeferredState } from './util/hooks'
2019-08-03 00:35:47 +09:00
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, '')
const [inputValue, setInputValue] = useState('')
2019-08-03 16:44:48 +09:00
const [suggested, setSuggested] = useState(false)
2019-09-01 01:28:24 +09:00
const inputRef = useRef<HTMLInputElement>(null)
2019-09-01 00:00:24 +09:00
const {
t,
i18n: { language },
} = useTranslation()
2019-07-27 19:18:54 +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)
}, [inputValue, setQuery])
2019-08-03 16:44:48 +09:00
useEffect(() => {
if (query.length === 0) {
setSuggested(false)
}
}, [query])
// set input value
2019-09-01 01:28:24 +09:00
function onInputChange(e: React.FormEvent<HTMLInputElement>) {
const value = e.currentTarget.value
2019-08-03 16:44:48 +09:00
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-09-01 01:28:24 +09:00
function onLogoClick(e: React.MouseEvent<HTMLDivElement>) {
setInputValue('')
2019-09-01 01:28:24 +09:00
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
2019-09-01 01:28:24 +09:00
function onSuggestionCompleted(name: string) {
2019-08-03 16:44:48 +09:00
setInputValue(name)
setSuggested(true)
}
2019-07-27 19:18:54 +09:00
return (
<>
<GlobalStyle />
2019-08-03 16:44:48 +09:00
2019-08-03 13:36:29 +09:00
<Helmet>
2019-08-14 13:04:34 +09:00
<title>namae {t('title')}</title>
2019-08-03 13:36:29 +09:00
</Helmet>
2019-08-03 16:44:48 +09:00
2019-08-02 04:23:21 +09:00
<Header>
<InputContainer>
<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
<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} />
2019-08-14 15:13:57 +09:00
<SpectrumCard query={query} />
2019-08-06 02:07:05 +09:00
<SlackCard query={query} />
2019-08-27 03:15:35 +09:00
<HerokuCard query={query} />
<NowCard query={query} />
2019-08-06 02:07:05 +09:00
<JsOrgCard query={query} />
2019-08-27 03:15:35 +09:00
<S3Card 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-09-01 00:00:24 +09:00
{language === 'ja' ? <NtaCard query={query} /> : null}
2019-08-05 22:59:39 +09:00
</Cards>
</SearchResult>
2019-08-07 17:42:07 +09:00
) : (
!isStandalone() && <Welcome />
)}
</Content>
2019-08-07 17:42:07 +09:00
<Footer />
</>
2019-07-27 19:18:54 +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;
}
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-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;
cursor: pointer;
${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;
}
`