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:
parent
1b872aabef
commit
3a90cd74cd
@ -14,5 +14,6 @@
|
||||
"s3": "AWS S3",
|
||||
"twitter": "Twitter",
|
||||
"slack": "Slack"
|
||||
}
|
||||
},
|
||||
"try": "suggestion"
|
||||
}
|
||||
|
@ -14,5 +14,6 @@
|
||||
"s3": "AWS S3",
|
||||
"twitter": "Twitter",
|
||||
"slack": "Slack"
|
||||
}
|
||||
},
|
||||
"try": "これはどう?"
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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}>
|
||||
|
69
web/src/components/Suggestion.js
Normal file
69
web/src/components/Suggestion.js
Normal 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;
|
||||
`
|
@ -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}
|
||||
|
@ -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`,
|
||||
|
@ -12,7 +12,6 @@ export default function GithubCard({ name }) {
|
||||
return (
|
||||
<Card
|
||||
title={t('providers.github')}
|
||||
key={name}
|
||||
nameList={[name]}
|
||||
alternativeList={[
|
||||
`${lowerCase}hq`,
|
||||
|
@ -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
|
||||
|
@ -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`}
|
||||
|
@ -11,7 +11,6 @@ export default function NpmCard({ name }) {
|
||||
return (
|
||||
<Card
|
||||
title={t('providers.npm')}
|
||||
key={lowerCase}
|
||||
nameList={[lowerCase]}
|
||||
alternativeList={[`${lowerCase}-js`]}>
|
||||
{(name) => (
|
||||
|
@ -11,7 +11,6 @@ export default function PypiCard({ name }) {
|
||||
return (
|
||||
<Card
|
||||
title={t('providers.pypi')}
|
||||
key={name}
|
||||
nameList={[name]}
|
||||
alternativeList={[`Py${capitalize(name)}`]}>
|
||||
{(name) => (
|
||||
|
@ -10,7 +10,6 @@ export default function RubyGemsCard({ name }) {
|
||||
return (
|
||||
<Card
|
||||
title={t('providers.rubygems')}
|
||||
key={name}
|
||||
nameList={[name]}
|
||||
alternativeList={[`${name.toLowerCase()}-rb`]}>
|
||||
{(name) => (
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -11,7 +11,6 @@ export default function TwitterCard({ name }) {
|
||||
return (
|
||||
<Card
|
||||
title={t('providers.twitter')}
|
||||
key={name}
|
||||
nameList={[name]}
|
||||
alternativeList={[
|
||||
`${capitalize(name)}HQ`,
|
||||
|
@ -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(() => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user