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:
parent
769548ccc9
commit
6f87b7fc5b
@ -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;
|
||||
}
|
||||
`;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user