1
0
mirror of https://github.com/uetchy/namae.git synced 2025-07-01 14:00:03 +09:00

chore: add animation

This commit is contained in:
uetchy 2020-03-06 00:05:54 +09:00
parent 769548ccc9
commit 6f87b7fc5b
3 changed files with 80 additions and 37 deletions

View File

@ -4,24 +4,29 @@ import {useTranslation} from 'react-i18next';
import {Link, useHistory} from 'react-router-dom';
import {sanitize} from '../util/text';
import {sendQueryEvent} from '../util/analytics';
import {useDeferredState} from '../util/hooks';
import {mobile} from '../util/css';
import {mobile, slideUp} from '../util/css';
import Suggestion from './Suggestion';
import {useDeferredState} from '../util/hooks';
const Form: React.FC<{
initialValue?: string;
}> = ({initialValue = ''}) => {
const history = useHistory();
const [query, setQuery] = useDeferredState(800, '');
const [inputValue, setInputValue] = useState(initialValue);
const [suggestionQuery, setSuggestionQuery] = useDeferredState(800, '');
const [isFocused, setFocus] = useState(false);
const [suggested, setSuggested] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const {t} = useTranslation();
function search(query: string) {
sendQueryEvent(sanitize(query));
history.push(`/s/${query}`);
}
// set input value
function onInputChange(e: React.FormEvent<HTMLInputElement>): void {
const value = e.currentTarget.value;
setInputValue(value);
setInputValue(e.currentTarget.value);
}
// clear input form and focus on it
@ -34,53 +39,56 @@ const Form: React.FC<{
// invoke when user clicked one of the suggested items
function onSuggestionCompleted(name: string): void {
setInputValue(name);
search(name);
setSuggested(true);
}
const queryGiven = query && query.length > 0;
useEffect(() => {
function onQuery(query: string) {
if (!query || query === '') {
return;
}
sendQueryEvent(query);
history.push(`/s/${query}`);
function onSubmitQuery(e: React.FormEvent) {
e.preventDefault();
if (!inputValue || inputValue === '') {
return;
}
if (query.length === 0) {
setSuggested(false);
} else {
onQuery(query);
}
}, [query, history]);
search(inputValue);
}
useEffect(() => {
const modifiedValue = sanitize(inputValue);
setQuery(modifiedValue);
}, [inputValue, setQuery]);
setSuggestionQuery(modifiedValue);
}, [inputValue, setSuggestionQuery]);
const queryGiven = suggestionQuery && suggestionQuery !== '';
return (
<InputContainer>
<Logo to="/" onClick={onLogoClick}>
<LogoImage src="/logo.svg" />
</Logo>
<InputView
onChange={onInputChange}
value={inputValue}
ref={inputRef}
placeholder={t('placeholder')}
aria-label="search query"
/>
<form onSubmit={onSubmitQuery}>
<InputView
onChange={onInputChange}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
value={inputValue}
ref={inputRef}
placeholder={t('placeholder')}
aria-label="search form"
/>
{isFocused && (
<Tips>
<span>
Press <kbd>Enter</kbd> to search
</span>
</Tips>
)}
</form>
{queryGiven && !suggested ? (
<Suggestion onSubmit={onSuggestionCompleted} query={query} />
<Suggestion onSubmit={onSuggestionCompleted} query={suggestionQuery} />
) : null}
</InputContainer>
);
};
export default Form;
const InputContainer = styled.div`
display: flex;
flex-direction: column;
@ -120,7 +128,8 @@ const LogoImage = styled.img`
`;
const InputView = styled.input.attrs({
type: 'text',
type: 'input',
name: 'search',
autocomplete: 'off',
autocorrect: 'off',
autocapitalize: 'off',
@ -142,3 +151,15 @@ const InputView = styled.input.attrs({
color: #c8cdda;
}
`;
const Tips = styled.div`
color: #ababab;
text-align: center;
overflow: hidden;
span {
display: block;
animation: ${slideUp} 0.6s cubic-bezier(0.19, 1, 0.22, 1);
animation-fill-mode: both;
}
`;

View File

@ -7,7 +7,7 @@ import {motion} from 'framer-motion';
import {capitalize, stem, germanify, njoin, lower, upper} from '../util/text';
import {sampleFromArray, fillArray} from '../util/array';
import {mobile} from '../util/css';
import {mobile, slideUp} from '../util/css';
import {sanitize} from '../util/text';
import {
sendShuffleSuggestionEvent,
@ -225,8 +225,11 @@ const Suggestion: React.FC<{
<Items>
{bestWords &&
bestWords.map((name, i) => (
<Item key={name + i} onClick={(): void => applyQuery(name)}>
{name}
<Item
key={name + i}
onClick={(): void => applyQuery(name)}
delay={i + 1}>
<span>{name}</span>
</Item>
))}
</Items>
@ -276,7 +279,7 @@ const Items = styled.div`
}
`;
const Item = styled.div`
const Item = styled.div<{delay: number}>`
margin: 10px 10px 0;
cursor: pointer;
font-weight: bold;
@ -285,6 +288,14 @@ const Item = styled.div`
line-height: 1em;
border-bottom: 1px dashed black;
color: black;
overflow: hidden;
span {
display: block;
animation: ${slideUp} 0.6s ${(props) => `${props.delay * 0.1}s`}
cubic-bezier(0.19, 1, 0.22, 1);
animation-fill-mode: both;
}
${mobile} {
margin: 10px 0 0;

View File

@ -1 +1,12 @@
import {keyframes} from 'styled-components';
export const mobile = '@media screen and (max-width: 800px)';
export const slideUp = keyframes`
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
`;