1
0
mirror of https://github.com/uetchy/namae.git synced 2025-03-17 04:30:31 +09:00

chore: move components into separated files

This commit is contained in:
uetchy 2019-09-02 19:41:44 +09:00
parent 7c18e0643c
commit 2438518e3c
22 changed files with 211 additions and 180 deletions

View File

@ -1,131 +1,40 @@
import React, { useState, useEffect, useRef } from 'react'
import React, { useState } from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
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'
import NtaCard from './components/cards/nta'
import Welcome from './components/Welcome'
import Form from './components/Form'
import Cards from './components/cards'
import Footer from './components/Footer'
import Suggestion from './components/Suggestion'
import { useDeferredState } from './util/hooks'
import { mobile } from './util/css'
import { isStandalone } from './util/pwa'
export default function App() {
const [query, setQuery] = useDeferredState(1000, '')
const [inputValue, setInputValue] = useState('')
const [suggested, setSuggested] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const {
t,
i18n: { language },
} = useTranslation()
const [query, setQuery] = useState('')
const { t } = useTranslation()
const queryGiven = query && query.length > 0
useEffect(() => {
const modifiedValue = inputValue.replace(/[\s@+!#$%^&*()[\]]/g, '')
setQuery(modifiedValue)
}, [inputValue, setQuery])
useEffect(() => {
if (query.length === 0) {
setSuggested(false)
}
}, [query])
// set input value
function onInputChange(e: React.FormEvent<HTMLInputElement>) {
const value = e.currentTarget.value
setInputValue(value)
}
// clear input form and focus on it
function onLogoClick(e: React.MouseEvent<HTMLDivElement>) {
setInputValue('')
inputRef.current!.focus()
}
// invoke when user clicked one of the suggested items
function onSuggestionCompleted(name: string) {
setInputValue(name)
setSuggested(true)
function onQuery(query: string) {
setQuery(query)
}
return (
<>
<GlobalStyle />
<Helmet>
<title>namae {t('title')}</title>
</Helmet>
<Header>
<InputContainer>
<Logo onClick={onLogoClick}>namæ</Logo>
<InputView
onChange={onInputChange}
value={inputValue}
ref={inputRef}
placeholder={t('placeholder')}
aria-label="search query"
/>
{queryGiven && !suggested ? (
<Suggestion onSubmit={onSuggestionCompleted} query={query} />
) : null}
</InputContainer>
<Form onQuery={onQuery} />
</Header>
<Content>
{queryGiven ? (
<SearchResult>
<Cards>
<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} />
<SpectrumCard query={query} />
<SlackCard query={query} />
<HerokuCard query={query} />
<NowCard query={query} />
<JsOrgCard query={query} />
<S3Card query={query} />
</Cards>
<Cards>
<GithubSearchCard query={query} />
<AppStoreCard query={query} />
{language === 'ja' ? <NtaCard query={query} /> : null}
</Cards>
</SearchResult>
{query !== '' ? (
<Cards query={query} />
) : (
!isStandalone() && <Welcome />
)}
</Content>
<Footer />
</>
)
@ -171,62 +80,3 @@ const Header = styled.header`
padding: 0 20px;
}
`
const InputContainer = styled.div`
transform: translateY(40px);
padding: 20px;
background: #ffffff;
box-shadow: 0 10px 20px 0 #c7dcf7;
border-radius: 20px;
${mobile} {
transform: translateY(20px);
}
`
const Logo = styled.div`
margin-bottom: 5px;
text-align: center;
font-family: sans-serif;
font-weight: bold;
font-size: 20px;
color: #4a90e2;
cursor: pointer;
${mobile} {
font-size: 15px;
}
`
const InputView = styled.input.attrs({
type: 'text',
autocomplete: 'off',
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'false',
})`
width: 100%;
border: none;
outline: none;
text-align: center;
font-family: monospace;
font-size: 5rem;
line-height: 1.2em;
${mobile} {
font-size: 2rem;
}
`
const SearchResult = styled.div``
const Cards = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
${mobile} {
flex-direction: column;
}
`

112
web/src/components/Form.tsx Normal file
View File

@ -0,0 +1,112 @@
import React, { useState, useRef, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useDeferredState } from '../util/hooks'
import { mobile } from '../util/css'
import Suggestion from './Suggestion'
const Form: React.FC<{ onQuery: (query: string) => void }> = ({ onQuery }) => {
const [query, setQuery] = useDeferredState(800, '')
const [inputValue, setInputValue] = useState('')
const [suggested, setSuggested] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const { t } = useTranslation()
// set input value
function onInputChange(e: React.FormEvent<HTMLInputElement>) {
const value = e.currentTarget.value
setInputValue(value)
}
// clear input form and focus on it
function onLogoClick(e: React.MouseEvent<HTMLDivElement>) {
setInputValue('')
inputRef.current!.focus()
}
// invoke when user clicked one of the suggested items
function onSuggestionCompleted(name: string) {
setInputValue(name)
setSuggested(true)
}
const queryGiven = query && query.length > 0
useEffect(() => {
if (query.length === 0) {
setSuggested(false)
}
onQuery(query)
}, [query, onQuery])
useEffect(() => {
const modifiedValue = inputValue.replace(/[\s@+!#$%^&*()[\]]/g, '')
setQuery(modifiedValue)
}, [inputValue, setQuery])
return (
<InputContainer>
<Logo onClick={onLogoClick}>namæ</Logo>
<InputView
onChange={onInputChange}
value={inputValue}
ref={inputRef}
placeholder={t('placeholder')}
aria-label="search query"
/>
{queryGiven && !suggested ? (
<Suggestion onSubmit={onSuggestionCompleted} query={query} />
) : null}
</InputContainer>
)
}
export default Form
const InputContainer = styled.div`
transform: translateY(40px);
padding: 20px;
background: #ffffff;
box-shadow: 0 10px 20px 0 #c7dcf7;
border-radius: 20px;
${mobile} {
transform: translateY(20px);
}
`
const Logo = styled.div`
margin-bottom: 5px;
text-align: center;
font-family: sans-serif;
font-weight: bold;
font-size: 20px;
color: #4a90e2;
cursor: pointer;
${mobile} {
font-size: 15px;
}
`
const InputView = styled.input.attrs({
type: 'text',
autocomplete: 'off',
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'false',
})`
width: 100%;
border: none;
outline: none;
text-align: center;
font-family: monospace;
font-size: 5rem;
line-height: 1.2em;
${mobile} {
font-size: 2rem;
}
`

View File

@ -3,7 +3,7 @@ import useFetch from 'fetch-suspense'
import { useTranslation } from 'react-i18next'
import { FaAppStore, FaInfoCircle } from 'react-icons/fa'
import { Card, Result } from '../Cards'
import { Card, Result } from './core'
const Search: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { DiRust } from 'react-icons/di'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const CratesioCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaMapSigns } from 'react-icons/fa'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const DomainCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaGithub } from 'react-icons/fa'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const GithubCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -3,7 +3,7 @@ import useFetch from 'fetch-suspense'
import { useTranslation } from 'react-i18next'
import { FaGithub, FaInfoCircle } from 'react-icons/fa'
import { Card, Result } from '../Cards'
import { Card, Result } from './core'
const Search: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { DiHeroku } from 'react-icons/di'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const HerokuCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { IoIosBeer } from 'react-icons/io'
import { Card, Repeater, ExistentialAvailability } from '../Cards'
import { Card, Repeater, ExistentialAvailability } from './core'
const HomebrewCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaJsSquare } from 'react-icons/fa'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const JsOrgCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { DiUbuntu } from 'react-icons/di'
import { DiDebian } from 'react-icons/di'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const LinuxCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { NowIcon } from '../Icons'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const NowCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaNpm } from 'react-icons/fa'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const NpmCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -3,7 +3,7 @@ import useFetch from 'fetch-suspense'
import { useTranslation } from 'react-i18next'
import { FaBuilding, FaInfoCircle } from 'react-icons/fa'
import { Card, Result } from '../Cards'
import { Card, Result } from './core'
const Search: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { FaPython } from 'react-icons/fa'
import { capitalize } from '../../util/text'
import { Card, DedicatedAvailability, Repeater } from '../Cards'
import { Card, DedicatedAvailability, Repeater } from './core'
const PypiCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaGem } from 'react-icons/fa'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const RubyGemsCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaAws } from 'react-icons/fa'
import { Card, DedicatedAvailability, Repeater } from '../Cards'
import { Card, DedicatedAvailability, Repeater } from './core'
const S3Card: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaSlack } from 'react-icons/fa'
import { Card, DedicatedAvailability, Repeater } from '../Cards'
import { Card, DedicatedAvailability, Repeater } from './core'
const SlackCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -1,6 +1,6 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
import { SpectrumIcon } from '../Icons'
const SpectrumCard: React.FC<{ query: string }> = ({ query }) => {

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { FaTwitter } from 'react-icons/fa'
import { capitalize } from '../../util/text'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
import { Card, Repeater, DedicatedAvailability } from './core'
const TwitterCard: React.FC<{ query: string }> = ({ query }) => {
const { t } = useTranslation()

View File

@ -7,8 +7,8 @@ import BarLoader from 'react-spinners/BarLoader'
import { GoInfo } from 'react-icons/go'
import { useTranslation } from 'react-i18next'
import { ExternalLink } from './Links'
import { mobile } from '../util/css'
import { mobile } from '../../util/css'
import { ExternalLink } from '../Links'
const COLORS = {
available: '#6e00ff',

View File

@ -0,0 +1,69 @@
import React from 'react'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { mobile } from '../../util/css'
import DomainCard from './Domains'
import GithubCard from './GitHubRepository'
import NpmCard from './Npm'
import PypiCard from './PyPI'
import RubyGemsCard from './RubyGems'
import CratesioCard from './Cratesio'
import HomebrewCard from './Homebrew'
import LinuxCard from './Linux'
import TwitterCard from './Twitter'
import SpectrumCard from './Spectrum'
import SlackCard from './Slack'
import S3Card from './S3'
import JsOrgCard from './JsOrg'
import GithubSearchCard from './GitHubSearch'
import AppStoreCard from './AppStore'
import HerokuCard from './Heroku'
import NowCard from './Now'
import NtaCard from './Nta'
const Index: React.FC<{ query: string }> = ({ query }) => {
const {
i18n: { language },
} = useTranslation()
return (
<>
<Cards>
<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} />
<SpectrumCard query={query} />
<SlackCard query={query} />
<HerokuCard query={query} />
<NowCard query={query} />
<JsOrgCard query={query} />
<S3Card query={query} />
</Cards>
<Cards>
<GithubSearchCard query={query} />
<AppStoreCard query={query} />
{language === 'ja' ? <NtaCard query={query} /> : null}
</Cards>
</>
)
}
export default Index
const Cards = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
${mobile} {
flex-direction: column;
}
`