@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-react': patch
|
||||
---
|
||||
|
||||
Slight type tweak to match newer React versions better
|
||||
@@ -77,7 +77,8 @@ describe('<ErrorBoundary/>', () => {
|
||||
/^The above error occurred in the <Bomb> component:/,
|
||||
),
|
||||
expect.stringMatching(/^ErrorBoundary/),
|
||||
expect.stringMatching(/Warning: findDOMNode/), // React warning, unfortunate but currently true
|
||||
]);
|
||||
expect(error.length).toEqual(4);
|
||||
expect(error.length).toEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AsyncState } from 'react-use/esm/useAsync';
|
||||
import { AutocompleteProps } from '@material-ui/lab/Autocomplete';
|
||||
import { Extension } from '@backstage/core-plugin-api';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { LinkProps } from '@backstage/core-components';
|
||||
@@ -132,10 +131,23 @@ export type SearchAutocompleteProps<Option> = Omit<
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SearchBar: ForwardRefExoticComponent<SearchBarProps>;
|
||||
export const SearchBar: React_2.ForwardRefExoticComponent<
|
||||
Omit<
|
||||
Omit<Partial<SearchBarBaseProps>, 'ref'> &
|
||||
React_2.RefAttributes<HTMLDivElement>,
|
||||
'ref'
|
||||
> &
|
||||
React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps>;
|
||||
export const SearchBarBase: React_2.ForwardRefExoticComponent<
|
||||
Omit<
|
||||
Omit<SearchBarBaseProps, 'ref'> & React_2.RefAttributes<HTMLDivElement>,
|
||||
'ref'
|
||||
> &
|
||||
React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type SearchBarBaseProps = Omit<TextFieldProps, 'onChange'> & {
|
||||
|
||||
@@ -30,7 +30,6 @@ import React, {
|
||||
ChangeEvent,
|
||||
ComponentType,
|
||||
forwardRef,
|
||||
ForwardRefExoticComponent,
|
||||
KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
@@ -70,138 +69,137 @@ export type SearchBarBaseProps = Omit<TextFieldProps, 'onChange'> & {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps> =
|
||||
withContext(
|
||||
forwardRef((props, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown = () => {},
|
||||
onClear = () => {},
|
||||
onSubmit = () => {},
|
||||
debounceTime = 200,
|
||||
clearButton = true,
|
||||
fullWidth = true,
|
||||
value: defaultValue,
|
||||
label,
|
||||
placeholder,
|
||||
inputProps = {},
|
||||
InputProps = {},
|
||||
endAdornment,
|
||||
...rest
|
||||
} = props;
|
||||
export const SearchBarBase = withContext(
|
||||
forwardRef<HTMLDivElement, SearchBarBaseProps>((props, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown = () => {},
|
||||
onClear = () => {},
|
||||
onSubmit = () => {},
|
||||
debounceTime = 200,
|
||||
clearButton = true,
|
||||
fullWidth = true,
|
||||
value: defaultValue,
|
||||
label,
|
||||
placeholder,
|
||||
inputProps = {},
|
||||
InputProps = {},
|
||||
endAdornment,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const configApi = useApi(configApiRef);
|
||||
const [value, setValue] = useState<string>('');
|
||||
const forwardedValueRef = useRef<string>('');
|
||||
const configApi = useApi(configApiRef);
|
||||
const [value, setValue] = useState<string>('');
|
||||
const forwardedValueRef = useRef<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setValue(prevValue => {
|
||||
// We only update the value if our current value is the same as it was
|
||||
// for the most recent onChange call. Otherwise it means that the users
|
||||
// has continued typing and we should not replace their input.
|
||||
if (prevValue === forwardedValueRef.current) {
|
||||
return String(defaultValue);
|
||||
}
|
||||
return prevValue;
|
||||
});
|
||||
}, [defaultValue, forwardedValueRef]);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
forwardedValueRef.current = value;
|
||||
onChange(value);
|
||||
},
|
||||
debounceTime,
|
||||
[value],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (onKeyDown) onKeyDown(e);
|
||||
if (onSubmit && e.key === 'Enter') {
|
||||
onSubmit();
|
||||
}
|
||||
},
|
||||
[onKeyDown, onSubmit],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
forwardedValueRef.current = '';
|
||||
onChange('');
|
||||
setValue('');
|
||||
if (onClear) {
|
||||
onClear();
|
||||
useEffect(() => {
|
||||
setValue(prevValue => {
|
||||
// We only update the value if our current value is the same as it was
|
||||
// for the most recent onChange call. Otherwise it means that the users
|
||||
// has continued typing and we should not replace their input.
|
||||
if (prevValue === forwardedValueRef.current) {
|
||||
return String(defaultValue);
|
||||
}
|
||||
}, [onChange, onClear]);
|
||||
return prevValue;
|
||||
});
|
||||
}, [defaultValue, forwardedValueRef]);
|
||||
|
||||
const ariaLabel: string | undefined = label ? undefined : 'Search';
|
||||
useDebounce(
|
||||
() => {
|
||||
forwardedValueRef.current = value;
|
||||
onChange(value);
|
||||
},
|
||||
debounceTime,
|
||||
[value],
|
||||
);
|
||||
|
||||
const inputPlaceholder =
|
||||
placeholder ??
|
||||
`Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
|
||||
const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (onKeyDown) onKeyDown(e);
|
||||
if (onSubmit && e.key === 'Enter') {
|
||||
onSubmit();
|
||||
}
|
||||
},
|
||||
[onKeyDown, onSubmit],
|
||||
);
|
||||
|
||||
const startAdornment = (
|
||||
<InputAdornment position="start">
|
||||
<IconButton aria-label="Query" size="small" disabled>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
const handleClear = useCallback(() => {
|
||||
forwardedValueRef.current = '';
|
||||
onChange('');
|
||||
setValue('');
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
}, [onChange, onClear]);
|
||||
|
||||
const clearButtonEndAdornment = (
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
aria-label="Clear"
|
||||
size="small"
|
||||
onClick={handleClear}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
// write your functionality here
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
);
|
||||
const ariaLabel: string | undefined = label ? undefined : 'Search';
|
||||
|
||||
return (
|
||||
<TextField
|
||||
id="search-bar-text-field"
|
||||
data-testid="search-bar-next"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
inputRef={ref}
|
||||
value={value}
|
||||
label={label}
|
||||
placeholder={inputPlaceholder}
|
||||
InputProps={{
|
||||
startAdornment,
|
||||
endAdornment: clearButton ? clearButtonEndAdornment : endAdornment,
|
||||
...InputProps,
|
||||
const inputPlaceholder =
|
||||
placeholder ??
|
||||
`Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;
|
||||
|
||||
const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;
|
||||
|
||||
const startAdornment = (
|
||||
<InputAdornment position="start">
|
||||
<IconButton aria-label="Query" size="small" disabled>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
const clearButtonEndAdornment = (
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
aria-label="Clear"
|
||||
size="small"
|
||||
onClick={handleClear}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
// write your functionality here
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
inputProps={{
|
||||
'aria-label': ariaLabel,
|
||||
...inputProps,
|
||||
}}
|
||||
fullWidth={fullWidth}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
id="search-bar-text-field"
|
||||
data-testid="search-bar-next"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
inputRef={ref}
|
||||
value={value}
|
||||
label={label}
|
||||
placeholder={inputPlaceholder}
|
||||
InputProps={{
|
||||
startAdornment,
|
||||
endAdornment: clearButton ? clearButtonEndAdornment : endAdornment,
|
||||
...InputProps,
|
||||
}}
|
||||
inputProps={{
|
||||
'aria-label': ariaLabel,
|
||||
...inputProps,
|
||||
}}
|
||||
fullWidth={fullWidth}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for {@link SearchBar}.
|
||||
@@ -215,8 +213,8 @@ export type SearchBarProps = Partial<SearchBarBaseProps>;
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchBar: ForwardRefExoticComponent<SearchBarProps> = withContext(
|
||||
forwardRef((props, ref) => {
|
||||
export const SearchBar = withContext(
|
||||
forwardRef<HTMLDivElement, SearchBarProps>((props, ref) => {
|
||||
const { value: initialValue = '', onChange, ...rest } = props;
|
||||
|
||||
const { term, setTerm } = useSearch();
|
||||
|
||||
Reference in New Issue
Block a user