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

feat: add suggestion

This commit is contained in:
uetchy 2019-08-03 16:44:48 +09:00
parent 1b872aabef
commit 3a90cd74cd
17 changed files with 116 additions and 27 deletions

View File

@ -14,5 +14,6 @@
"s3": "AWS S3",
"twitter": "Twitter",
"slack": "Slack"
}
},
"try": "suggestion"
}

View File

@ -14,5 +14,6 @@
"s3": "AWS S3",
"twitter": "Twitter",
"slack": "Slack"
}
},
"try": "これはどう?"
}

View File

@ -3,8 +3,6 @@ import styled, { createGlobalStyle } from 'styled-components'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import Welcome from './components/Welcome'
import Footer from './components/Footer'
import { Cards, CardContainer } from './components/Cards'
import GithubCard from './components/cards/GithubCard'
import DomainCard from './components/cards/DomainCard'
@ -18,15 +16,19 @@ import S3Card from './components/cards/S3Card'
import CratesioCard from './components/cards/CratesioCard'
import RubyGemsCard from './components/cards/RubyGemsCard'
import { EventReporter } from './components/Analytics'
import Welcome from './components/Welcome'
import Footer from './components/Footer'
import Suggestion from './components/Suggestion'
import { useDeferredState } from './hooks/state'
import { mobile } from './util/css'
import { isStandalone } from './util/pwa'
export default function App() {
const [query, setQuery] = useDeferredState(1000)
const [query, setQuery] = useDeferredState('', 1000)
const [inputValue, setInputValue] = useState('')
const inputRef = useRef()
const [suggested, setSuggested] = useState(false)
const { t } = useTranslation()
const queryGiven = query && query.length > 0
@ -35,32 +37,53 @@ export default function App() {
setQuery(inputValue)
}, [inputValue, setQuery])
useEffect(() => {
if (query.length === 0) {
setSuggested(false)
}
}, [query])
// set input value
function onInputChange(e) {
setInputValue(e.target.value)
const value = e.target.value
setInputValue(value)
}
// clear input form and focus on it
function onLogoClick(e) {
setInputValue('')
inputRef.current.focus()
}
// invoke when user clicked one of the suggested items
function onSuggestionCompleted(name) {
setInputValue(name)
setSuggested(true)
}
return (
<>
<GlobalStyle />
<Helmet>
<title>namaæ {t('title')}</title>
</Helmet>
<Header>
<InputContainer>
<Logo onClick={onLogoClick}>namæ</Logo>
<Input
<InputView
onChange={onInputChange}
value={inputValue}
ref={inputRef}
placeholder={t('placeholder')}
/>
{queryGiven && !suggested ? (
<Suggestion onSubmit={onSuggestionCompleted} query={query} />
) : null}
</InputContainer>
</Header>
<Content>
{queryGiven ? (
<Cards>
@ -155,7 +178,7 @@ const Logo = styled.div`
}
`
const Input = styled.input.attrs({
const InputView = styled.input.attrs({
type: 'text',
autocomplete: 'off',
autocorrect: 'off',

View File

@ -16,11 +16,15 @@ export function Card({ title, nameList = [], alternativeList = [], children }) {
<CardTitle>{title}</CardTitle>
<CardList>
{nameList.map((name) => (
<AvailabilityContainer>{children(name)}</AvailabilityContainer>
<AvailabilityContainer key={name}>
{children(name)}
</AvailabilityContainer>
))}
{revealAlternatives &&
alternativeList.map((name) => (
<AvailabilityContainer>{children(name)}</AvailabilityContainer>
<AvailabilityContainer key={name}>
{children(name)}
</AvailabilityContainer>
))}
{alternativeList.length > 0 && !revealAlternatives ? (
<ShowAlternativesButton onClick={onClick}>

View File

@ -0,0 +1,69 @@
import React from 'react'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { capitalize } from '../util/text'
export default function Suggestion({ query, onSubmit }) {
const { t } = useTranslation()
const capital = capitalize(query)
const lower = query.toLowerCase()
const suggestions = [
`${lower}ify`,
`insta${lower}`,
`lib${lower}`,
`omni${lower}`,
`${capital}Lab`,
`${capital}Kit`,
`Open${capital}`,
]
.sort(() => Math.random() - 0.5)
.slice(0, 3)
function applyQuery(name) {
onSubmit(name)
}
return (
<Container>
<Title>{t('try')}</Title>
<Items>
{suggestions.map((name) => (
<Item key={name} onClick={() => applyQuery(name)}>
{name}
</Item>
))}
</Items>
</Container>
)
}
const Container = styled.div`
margin-bottom: 10px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
line-height: 1em;
`
const Title = styled.div`
margin-top: 15px;
border: 1px solid black;
padding: 1px 6px;
border-radius: 20px;
font-size: 0.6em;
`
const Items = styled.div`
margin-top: 15px;
margin-left: 8px;
display: flex;
flex-direction: row;
`
const Item = styled.div`
margin-right: 8px;
cursor: pointer;
font-weight: bold;
`

View File

@ -9,7 +9,7 @@ export default function CratesioCard({ name }) {
const lowerCase = name.toLowerCase()
return (
<Card title={t('providers.rust')} key={lowerCase} nameList={[lowerCase]}>
<Card title={t('providers.rust')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}

View File

@ -11,7 +11,6 @@ export default function DomainCard({ name }) {
return (
<Card
title={t('providers.domains')}
key={lowerCase}
nameList={[`${lowerCase}.com`, `${lowerCase}app.com`, `${lowerCase}.app`]}
alternativeList={[
`${lowerCase}.dev`,

View File

@ -12,7 +12,6 @@ export default function GithubCard({ name }) {
return (
<Card
title={t('providers.github')}
key={name}
nameList={[name]}
alternativeList={[
`${lowerCase}hq`,

View File

@ -9,10 +9,7 @@ export default function HomebrewCard({ name }) {
const lowerCase = name.toLowerCase()
return (
<Card
title={t('providers.homebrew')}
key={lowerCase}
nameList={[lowerCase]}>
<Card title={t('providers.homebrew')} nameList={[lowerCase]}>
{(name) => (
<>
<ExistentialAvailability

View File

@ -9,7 +9,7 @@ export default function JsOrgCard({ name }) {
const lowerCase = name.toLowerCase()
return (
<Card title={t('providers.jsorg')} key={lowerCase} nameList={[lowerCase]}>
<Card title={t('providers.jsorg')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={`${name}.js.org`}

View File

@ -11,7 +11,6 @@ export default function NpmCard({ name }) {
return (
<Card
title={t('providers.npm')}
key={lowerCase}
nameList={[lowerCase]}
alternativeList={[`${lowerCase}-js`]}>
{(name) => (

View File

@ -11,7 +11,6 @@ export default function PypiCard({ name }) {
return (
<Card
title={t('providers.pypi')}
key={name}
nameList={[name]}
alternativeList={[`Py${capitalize(name)}`]}>
{(name) => (

View File

@ -10,7 +10,6 @@ export default function RubyGemsCard({ name }) {
return (
<Card
title={t('providers.rubygems')}
key={name}
nameList={[name]}
alternativeList={[`${name.toLowerCase()}-rb`]}>
{(name) => (

View File

@ -9,7 +9,7 @@ export default function S3Card({ name }) {
const lowerCase = name.toLowerCase()
return (
<Card title={t('providers.s3')} key={lowerCase} nameList={[lowerCase]}>
<Card title={t('providers.s3')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}

View File

@ -9,7 +9,7 @@ export default function SlackCard({ name }) {
const lowerCase = name.toLowerCase()
return (
<Card title={t('providers.slack')} key={lowerCase} nameList={[lowerCase]}>
<Card title={t('providers.slack')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}

View File

@ -11,7 +11,6 @@ export default function TwitterCard({ name }) {
return (
<Card
title={t('providers.twitter')}
key={name}
nameList={[name]}
alternativeList={[
`${capitalize(name)}HQ`,

View File

@ -1,8 +1,8 @@
import { useState, useEffect } from 'react'
export function useDeferredState(duration) {
const [response, setResponse] = useState()
const [innerValue, setInnerValue] = useState()
export function useDeferredState(initialValue = undefined, duration = 1000) {
const [response, setResponse] = useState(initialValue)
const [innerValue, setInnerValue] = useState(initialValue)
useEffect(() => {
const fn = setTimeout(() => {