kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Created search-zap-split
rodzic
d4a8d96246
commit
47675e814f
|
@ -0,0 +1,168 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
changeSearch,
|
||||||
|
clearSearchResults,
|
||||||
|
setSearchAccount,
|
||||||
|
showSearch,
|
||||||
|
submitSearch,
|
||||||
|
} from 'soapbox/actions/search';
|
||||||
|
import AutosuggestAccountInput from 'soapbox/components/autosuggest-account-input';
|
||||||
|
import { Input } from 'soapbox/components/ui';
|
||||||
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { Account } from 'soapbox/schemas';
|
||||||
|
import { selectAccount } from 'soapbox/selectors';
|
||||||
|
import { RootState } from 'soapbox/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||||
|
action: { id: 'search.action', defaultMessage: 'Search for “{query}”' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ISearchZapSplit {
|
||||||
|
autoFocus?: boolean;
|
||||||
|
autoSubmit?: boolean;
|
||||||
|
autosuggest?: boolean;
|
||||||
|
openInRoute?: boolean;
|
||||||
|
onChange: (account: Account | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchZapSplit = (props: ISearchZapSplit) => {
|
||||||
|
const {
|
||||||
|
autoFocus = false,
|
||||||
|
autoSubmit = false,
|
||||||
|
autosuggest = false,
|
||||||
|
openInRoute = false,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const history = useHistory();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const value = useAppSelector((state) => state.search.value);
|
||||||
|
const submitted = useAppSelector((state) => state.search.submitted);
|
||||||
|
|
||||||
|
const debouncedSubmit = useCallback(debounce(() => {
|
||||||
|
dispatch(submitSearch());
|
||||||
|
}, 900), []);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
dispatch(changeSearch(value));
|
||||||
|
|
||||||
|
if (autoSubmit) {
|
||||||
|
debouncedSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (value.length > 0 || submitted) {
|
||||||
|
dispatch(clearSearchResults());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (openInRoute) {
|
||||||
|
dispatch(setSearchAccount(null));
|
||||||
|
dispatch(submitSearch());
|
||||||
|
|
||||||
|
history.push('/search');
|
||||||
|
} else {
|
||||||
|
dispatch(submitSearch());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
handleSubmit();
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
document.querySelector('.ui')?.parentElement?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
dispatch(showSearch());
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAccount = (accountId: string) => (dispatch: any, getState: () => RootState) => {
|
||||||
|
const account = selectAccount(getState(), accountId);
|
||||||
|
console.log(account);
|
||||||
|
|
||||||
|
props.onChange(account!);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelected = (accountId: string) => {
|
||||||
|
dispatch(getAccount(accountId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasValue = value.length > 0 || submitted;
|
||||||
|
const componentProps: any = {
|
||||||
|
type: 'text',
|
||||||
|
id: 'search',
|
||||||
|
placeholder: intl.formatMessage(messages.placeholder),
|
||||||
|
value,
|
||||||
|
onChange: handleChange,
|
||||||
|
onKeyDown: handleKeyDown,
|
||||||
|
onFocus: handleFocus,
|
||||||
|
autoFocus: autoFocus,
|
||||||
|
theme: 'normal',
|
||||||
|
className: 'pr-10 rtl:pl-10 rtl:pr-3',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (autosuggest) {
|
||||||
|
componentProps.onSelected = handleSelected;
|
||||||
|
componentProps.autoSelect = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const newPath = history.location.pathname;
|
||||||
|
const shouldPersistSearch = !!newPath.match(/@.+\/posts\/[a-zA-Z0-9]+/g)
|
||||||
|
|| !!newPath.match(/\/tags\/.+/g);
|
||||||
|
|
||||||
|
if (!shouldPersistSearch) {
|
||||||
|
dispatch(changeSearch(''));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full'>
|
||||||
|
<label htmlFor='search' className='sr-only'>{intl.formatMessage(messages.placeholder)}</label>
|
||||||
|
|
||||||
|
<div className='relative'>
|
||||||
|
{autosuggest ? (
|
||||||
|
<AutosuggestAccountInput {...componentProps} />
|
||||||
|
) : (
|
||||||
|
<Input theme='normal' {...componentProps} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto'
|
||||||
|
onClick={handleClear}
|
||||||
|
>
|
||||||
|
|
||||||
|
<SvgIcon
|
||||||
|
src={require('@tabler/icons/outline/x.svg')}
|
||||||
|
className={clsx('h-4 w-4 text-gray-600', { hidden: !hasValue })}
|
||||||
|
aria-label={intl.formatMessage(messages.placeholder)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchZapSplit;
|
Ładowanie…
Reference in New Issue