mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
164 lines
4.2 KiB
TypeScript
164 lines
4.2 KiB
TypeScript
import {Component} from 'react';
|
|
import type {WithRouterProps} from 'react-router';
|
|
import styled from '@emotion/styled';
|
|
import debounce from 'lodash/debounce';
|
|
|
|
import type {Client, ResponseMeta} from 'sentry/api';
|
|
import Input from 'sentry/components/input';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import {t} from 'sentry/locale';
|
|
// eslint-disable-next-line no-restricted-imports
|
|
import withSentryRouter from 'sentry/utils/withSentryRouter';
|
|
|
|
type RenderProps = {
|
|
busy: boolean;
|
|
defaultSearchBar: React.ReactNode;
|
|
handleChange: (value: string) => void;
|
|
value: string;
|
|
};
|
|
|
|
type DefaultProps = {
|
|
/**
|
|
* Placeholder text in the search input
|
|
*/
|
|
placeholder: string;
|
|
/**
|
|
* Time in milliseconds to wait before firing off the request
|
|
*/
|
|
debounceWait?: number; // optional, otherwise app/views/settings/organizationMembers/organizationMembersList.tsx L:191 is not happy
|
|
};
|
|
|
|
type Props = WithRouterProps &
|
|
DefaultProps & {
|
|
api: Client;
|
|
onError: () => void;
|
|
onSuccess: (data: object, resp: ResponseMeta | undefined) => void;
|
|
/**
|
|
* URL to make the search request to
|
|
*/
|
|
url: string;
|
|
|
|
/**
|
|
* A render-prop child may be passed to handle custom rendering of the input.
|
|
*/
|
|
children?: (otps: RenderProps) => React.ReactNode;
|
|
className?: string;
|
|
onSearchSubmit?: (query: string, event: React.FormEvent) => void;
|
|
|
|
/**
|
|
* Updates URL with search query in the URL param: `query`
|
|
*/
|
|
updateRoute?: boolean;
|
|
};
|
|
|
|
type State = {
|
|
busy: boolean;
|
|
query: string;
|
|
};
|
|
|
|
/**
|
|
* This is a search input that can be easily used in AsyncComponent/Views.
|
|
*
|
|
* It probably doesn't make too much sense outside of an AsyncComponent atm.
|
|
*/
|
|
class AsyncComponentSearchInput extends Component<Props, State> {
|
|
static defaultProps: DefaultProps = {
|
|
placeholder: t('Search...'),
|
|
debounceWait: 200,
|
|
};
|
|
|
|
state: State = {
|
|
query: '',
|
|
busy: false,
|
|
};
|
|
|
|
immediateQuery = async (searchQuery: string) => {
|
|
const {location, api} = this.props;
|
|
this.setState({busy: true});
|
|
|
|
try {
|
|
const [data, , resp] = await api.requestPromise(`${this.props.url}`, {
|
|
includeAllArgs: true,
|
|
method: 'GET',
|
|
query: {...location.query, query: searchQuery},
|
|
});
|
|
// only update data if the request's query matches the current query
|
|
if (this.state.query === searchQuery) {
|
|
this.props.onSuccess(data, resp);
|
|
}
|
|
} catch {
|
|
this.props.onError();
|
|
}
|
|
|
|
this.setState({busy: false});
|
|
};
|
|
|
|
query = debounce(this.immediateQuery, this.props.debounceWait);
|
|
|
|
handleChange = (query: string) => {
|
|
this.query(query);
|
|
this.setState({query});
|
|
};
|
|
|
|
handleInputChange = (evt: React.ChangeEvent<HTMLInputElement>) =>
|
|
this.handleChange(evt.target.value);
|
|
|
|
/**
|
|
* This is called when "Enter" (more specifically a form "submit" event) is pressed.
|
|
*/
|
|
handleSearch = (evt: React.FormEvent<HTMLFormElement>) => {
|
|
const {updateRoute, onSearchSubmit} = this.props;
|
|
evt.preventDefault();
|
|
|
|
// Update the URL to reflect search term.
|
|
if (updateRoute) {
|
|
const {router, location} = this.props;
|
|
router.push({
|
|
pathname: location.pathname,
|
|
query: {
|
|
query: this.state.query,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (typeof onSearchSubmit !== 'function') {
|
|
return;
|
|
}
|
|
onSearchSubmit(this.state.query, evt);
|
|
};
|
|
|
|
render() {
|
|
const {placeholder, children, className} = this.props;
|
|
const {busy, query} = this.state;
|
|
|
|
const defaultSearchBar = (
|
|
<Form onSubmit={this.handleSearch}>
|
|
<Input
|
|
value={query}
|
|
onChange={this.handleInputChange}
|
|
className={className}
|
|
placeholder={placeholder}
|
|
/>
|
|
{busy && <StyledLoadingIndicator size={18} hideMessage mini />}
|
|
</Form>
|
|
);
|
|
|
|
return children === undefined
|
|
? defaultSearchBar
|
|
: children({defaultSearchBar, busy, value: query, handleChange: this.handleChange});
|
|
}
|
|
}
|
|
|
|
const StyledLoadingIndicator = styled(LoadingIndicator)`
|
|
position: absolute;
|
|
right: 25px;
|
|
top: 50%;
|
|
transform: translateY(-13px);
|
|
`;
|
|
|
|
const Form = styled('form')`
|
|
position: relative;
|
|
`;
|
|
|
|
export default withSentryRouter(AsyncComponentSearchInput);
|