refactor: apply review suggestions
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { AutocompleteProps } from '@material-ui/lab';
|
||||
import { AutocompleteRenderInputParams } from '@material-ui/lab';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { InputBaseProps } from '@material-ui/core';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { ListItemTextProps } from '@material-ui/core';
|
||||
@@ -78,13 +78,11 @@ export interface SearchApi {
|
||||
export const searchApiRef: ApiRef<SearchApi>;
|
||||
|
||||
// @public
|
||||
export const SearchAutocomplete: <
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
>(
|
||||
props: SearchAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
export const SearchAutocomplete: SearchAutocompleteComponent;
|
||||
|
||||
// @public
|
||||
export type SearchAutocompleteComponent = <Option>(
|
||||
props: SearchAutocompleteProps<Option>,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
@@ -115,596 +113,20 @@ export type SearchAutocompleteFilterProps = SearchFilterComponentProps & {
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SearchAutocompleteProps<
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
> = Omit<
|
||||
AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
'renderInput'
|
||||
export type SearchAutocompleteProps<Option> = Omit<
|
||||
AutocompleteProps<Option, undefined, undefined, boolean>,
|
||||
'renderInput' | 'disableClearable' | 'multiple'
|
||||
> & {
|
||||
'data-testid'?: string;
|
||||
inputPlaceholder?: SearchBarProps['placeholder'];
|
||||
inputDebounceTime?: SearchBarProps['debounceTime'];
|
||||
renderInput?: (params: AutocompleteRenderInputParams) => JSX.Element;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SearchBar: React_2.ForwardRefExoticComponent<
|
||||
Pick<
|
||||
Partial<SearchBarBaseProps>,
|
||||
| 'required'
|
||||
| 'type'
|
||||
| 'error'
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'color'
|
||||
| 'margin'
|
||||
| 'translate'
|
||||
| 'value'
|
||||
| 'hidden'
|
||||
| 'dir'
|
||||
| 'slot'
|
||||
| 'style'
|
||||
| 'title'
|
||||
| 'accessKey'
|
||||
| 'draggable'
|
||||
| 'lang'
|
||||
| 'className'
|
||||
| 'prefix'
|
||||
| 'contentEditable'
|
||||
| 'inputMode'
|
||||
| 'tabIndex'
|
||||
| 'disabled'
|
||||
| 'autoComplete'
|
||||
| 'autoFocus'
|
||||
| 'defaultChecked'
|
||||
| 'defaultValue'
|
||||
| 'suppressContentEditableWarning'
|
||||
| 'suppressHydrationWarning'
|
||||
| 'contextMenu'
|
||||
| 'placeholder'
|
||||
| 'spellCheck'
|
||||
| 'radioGroup'
|
||||
| 'role'
|
||||
| 'about'
|
||||
| 'datatype'
|
||||
| 'inlist'
|
||||
| 'property'
|
||||
| 'resource'
|
||||
| 'typeof'
|
||||
| 'vocab'
|
||||
| 'autoCapitalize'
|
||||
| 'autoCorrect'
|
||||
| 'autoSave'
|
||||
| 'itemProp'
|
||||
| 'itemScope'
|
||||
| 'itemType'
|
||||
| 'itemID'
|
||||
| 'itemRef'
|
||||
| 'results'
|
||||
| 'security'
|
||||
| 'unselectable'
|
||||
| 'is'
|
||||
| 'aria-activedescendant'
|
||||
| 'aria-atomic'
|
||||
| 'aria-autocomplete'
|
||||
| 'aria-busy'
|
||||
| 'aria-checked'
|
||||
| 'aria-colcount'
|
||||
| 'aria-colindex'
|
||||
| 'aria-colspan'
|
||||
| 'aria-controls'
|
||||
| 'aria-current'
|
||||
| 'aria-describedby'
|
||||
| 'aria-details'
|
||||
| 'aria-disabled'
|
||||
| 'aria-dropeffect'
|
||||
| 'aria-errormessage'
|
||||
| 'aria-expanded'
|
||||
| 'aria-flowto'
|
||||
| 'aria-grabbed'
|
||||
| 'aria-haspopup'
|
||||
| 'aria-hidden'
|
||||
| 'aria-invalid'
|
||||
| 'aria-keyshortcuts'
|
||||
| 'aria-label'
|
||||
| 'aria-labelledby'
|
||||
| 'aria-level'
|
||||
| 'aria-live'
|
||||
| 'aria-modal'
|
||||
| 'aria-multiline'
|
||||
| 'aria-multiselectable'
|
||||
| 'aria-orientation'
|
||||
| 'aria-owns'
|
||||
| 'aria-placeholder'
|
||||
| 'aria-posinset'
|
||||
| 'aria-pressed'
|
||||
| 'aria-readonly'
|
||||
| 'aria-relevant'
|
||||
| 'aria-required'
|
||||
| 'aria-roledescription'
|
||||
| 'aria-rowcount'
|
||||
| 'aria-rowindex'
|
||||
| 'aria-rowspan'
|
||||
| 'aria-selected'
|
||||
| 'aria-setsize'
|
||||
| 'aria-sort'
|
||||
| 'aria-valuemax'
|
||||
| 'aria-valuemin'
|
||||
| 'aria-valuenow'
|
||||
| 'aria-valuetext'
|
||||
| 'readOnly'
|
||||
| 'rows'
|
||||
| 'dangerouslySetInnerHTML'
|
||||
| 'onCopy'
|
||||
| 'onCopyCapture'
|
||||
| 'onCut'
|
||||
| 'onCutCapture'
|
||||
| 'onPaste'
|
||||
| 'onPasteCapture'
|
||||
| 'onCompositionEnd'
|
||||
| 'onCompositionEndCapture'
|
||||
| 'onCompositionStart'
|
||||
| 'onCompositionStartCapture'
|
||||
| 'onCompositionUpdate'
|
||||
| 'onCompositionUpdateCapture'
|
||||
| 'onFocus'
|
||||
| 'onFocusCapture'
|
||||
| 'onBlur'
|
||||
| 'onBlurCapture'
|
||||
| 'onChange'
|
||||
| 'onChangeCapture'
|
||||
| 'onBeforeInput'
|
||||
| 'onBeforeInputCapture'
|
||||
| 'onInput'
|
||||
| 'onInputCapture'
|
||||
| 'onReset'
|
||||
| 'onResetCapture'
|
||||
| 'onSubmit'
|
||||
| 'onSubmitCapture'
|
||||
| 'onInvalid'
|
||||
| 'onInvalidCapture'
|
||||
| 'onLoad'
|
||||
| 'onLoadCapture'
|
||||
| 'onError'
|
||||
| 'onErrorCapture'
|
||||
| 'onKeyDown'
|
||||
| 'onKeyDownCapture'
|
||||
| 'onKeyPress'
|
||||
| 'onKeyPressCapture'
|
||||
| 'onKeyUp'
|
||||
| 'onKeyUpCapture'
|
||||
| 'onAbort'
|
||||
| 'onAbortCapture'
|
||||
| 'onCanPlay'
|
||||
| 'onCanPlayCapture'
|
||||
| 'onCanPlayThrough'
|
||||
| 'onCanPlayThroughCapture'
|
||||
| 'onDurationChange'
|
||||
| 'onDurationChangeCapture'
|
||||
| 'onEmptied'
|
||||
| 'onEmptiedCapture'
|
||||
| 'onEncrypted'
|
||||
| 'onEncryptedCapture'
|
||||
| 'onEnded'
|
||||
| 'onEndedCapture'
|
||||
| 'onLoadedData'
|
||||
| 'onLoadedDataCapture'
|
||||
| 'onLoadedMetadata'
|
||||
| 'onLoadedMetadataCapture'
|
||||
| 'onLoadStart'
|
||||
| 'onLoadStartCapture'
|
||||
| 'onPause'
|
||||
| 'onPauseCapture'
|
||||
| 'onPlay'
|
||||
| 'onPlayCapture'
|
||||
| 'onPlaying'
|
||||
| 'onPlayingCapture'
|
||||
| 'onProgress'
|
||||
| 'onProgressCapture'
|
||||
| 'onRateChange'
|
||||
| 'onRateChangeCapture'
|
||||
| 'onSeeked'
|
||||
| 'onSeekedCapture'
|
||||
| 'onSeeking'
|
||||
| 'onSeekingCapture'
|
||||
| 'onStalled'
|
||||
| 'onStalledCapture'
|
||||
| 'onSuspend'
|
||||
| 'onSuspendCapture'
|
||||
| 'onTimeUpdate'
|
||||
| 'onTimeUpdateCapture'
|
||||
| 'onVolumeChange'
|
||||
| 'onVolumeChangeCapture'
|
||||
| 'onWaiting'
|
||||
| 'onWaitingCapture'
|
||||
| 'onAuxClick'
|
||||
| 'onAuxClickCapture'
|
||||
| 'onClick'
|
||||
| 'onClickCapture'
|
||||
| 'onContextMenu'
|
||||
| 'onContextMenuCapture'
|
||||
| 'onDoubleClick'
|
||||
| 'onDoubleClickCapture'
|
||||
| 'onDrag'
|
||||
| 'onDragCapture'
|
||||
| 'onDragEnd'
|
||||
| 'onDragEndCapture'
|
||||
| 'onDragEnter'
|
||||
| 'onDragEnterCapture'
|
||||
| 'onDragExit'
|
||||
| 'onDragExitCapture'
|
||||
| 'onDragLeave'
|
||||
| 'onDragLeaveCapture'
|
||||
| 'onDragOver'
|
||||
| 'onDragOverCapture'
|
||||
| 'onDragStart'
|
||||
| 'onDragStartCapture'
|
||||
| 'onDrop'
|
||||
| 'onDropCapture'
|
||||
| 'onMouseDown'
|
||||
| 'onMouseDownCapture'
|
||||
| 'onMouseEnter'
|
||||
| 'onMouseLeave'
|
||||
| 'onMouseMove'
|
||||
| 'onMouseMoveCapture'
|
||||
| 'onMouseOut'
|
||||
| 'onMouseOutCapture'
|
||||
| 'onMouseOver'
|
||||
| 'onMouseOverCapture'
|
||||
| 'onMouseUp'
|
||||
| 'onMouseUpCapture'
|
||||
| 'onSelect'
|
||||
| 'onSelectCapture'
|
||||
| 'onTouchCancel'
|
||||
| 'onTouchCancelCapture'
|
||||
| 'onTouchEnd'
|
||||
| 'onTouchEndCapture'
|
||||
| 'onTouchMove'
|
||||
| 'onTouchMoveCapture'
|
||||
| 'onTouchStart'
|
||||
| 'onTouchStartCapture'
|
||||
| 'onPointerDown'
|
||||
| 'onPointerDownCapture'
|
||||
| 'onPointerMove'
|
||||
| 'onPointerMoveCapture'
|
||||
| 'onPointerUp'
|
||||
| 'onPointerUpCapture'
|
||||
| 'onPointerCancel'
|
||||
| 'onPointerCancelCapture'
|
||||
| 'onPointerEnter'
|
||||
| 'onPointerEnterCapture'
|
||||
| 'onPointerLeave'
|
||||
| 'onPointerLeaveCapture'
|
||||
| 'onPointerOver'
|
||||
| 'onPointerOverCapture'
|
||||
| 'onPointerOut'
|
||||
| 'onPointerOutCapture'
|
||||
| 'onGotPointerCapture'
|
||||
| 'onGotPointerCaptureCapture'
|
||||
| 'onLostPointerCapture'
|
||||
| 'onLostPointerCaptureCapture'
|
||||
| 'onScroll'
|
||||
| 'onScrollCapture'
|
||||
| 'onWheel'
|
||||
| 'onWheelCapture'
|
||||
| 'onAnimationStart'
|
||||
| 'onAnimationStartCapture'
|
||||
| 'onAnimationEnd'
|
||||
| 'onAnimationEndCapture'
|
||||
| 'onAnimationIteration'
|
||||
| 'onAnimationIterationCapture'
|
||||
| 'onTransitionEnd'
|
||||
| 'onTransitionEndCapture'
|
||||
| 'classes'
|
||||
| 'innerRef'
|
||||
| 'fullWidth'
|
||||
| 'inputProps'
|
||||
| 'inputRef'
|
||||
| 'multiline'
|
||||
| 'endAdornment'
|
||||
| 'inputComponent'
|
||||
| 'renderSuffix'
|
||||
| 'rowsMax'
|
||||
| 'rowsMin'
|
||||
| 'maxRows'
|
||||
| 'minRows'
|
||||
| 'startAdornment'
|
||||
| 'onClear'
|
||||
| 'debounceTime'
|
||||
| 'clearButton'
|
||||
> &
|
||||
React_2.RefAttributes<unknown>
|
||||
>;
|
||||
export const SearchBar: ForwardRefExoticComponent<SearchBarProps>;
|
||||
|
||||
// @public
|
||||
export const SearchBarBase: React_2.ForwardRefExoticComponent<
|
||||
Pick<
|
||||
SearchBarBaseProps,
|
||||
| 'required'
|
||||
| 'type'
|
||||
| 'error'
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'color'
|
||||
| 'margin'
|
||||
| 'translate'
|
||||
| 'value'
|
||||
| 'hidden'
|
||||
| 'dir'
|
||||
| 'slot'
|
||||
| 'style'
|
||||
| 'title'
|
||||
| 'accessKey'
|
||||
| 'draggable'
|
||||
| 'lang'
|
||||
| 'className'
|
||||
| 'prefix'
|
||||
| 'contentEditable'
|
||||
| 'inputMode'
|
||||
| 'tabIndex'
|
||||
| 'disabled'
|
||||
| 'autoComplete'
|
||||
| 'autoFocus'
|
||||
| 'defaultChecked'
|
||||
| 'defaultValue'
|
||||
| 'suppressContentEditableWarning'
|
||||
| 'suppressHydrationWarning'
|
||||
| 'contextMenu'
|
||||
| 'placeholder'
|
||||
| 'spellCheck'
|
||||
| 'radioGroup'
|
||||
| 'role'
|
||||
| 'about'
|
||||
| 'datatype'
|
||||
| 'inlist'
|
||||
| 'property'
|
||||
| 'resource'
|
||||
| 'typeof'
|
||||
| 'vocab'
|
||||
| 'autoCapitalize'
|
||||
| 'autoCorrect'
|
||||
| 'autoSave'
|
||||
| 'itemProp'
|
||||
| 'itemScope'
|
||||
| 'itemType'
|
||||
| 'itemID'
|
||||
| 'itemRef'
|
||||
| 'results'
|
||||
| 'security'
|
||||
| 'unselectable'
|
||||
| 'is'
|
||||
| 'aria-activedescendant'
|
||||
| 'aria-atomic'
|
||||
| 'aria-autocomplete'
|
||||
| 'aria-busy'
|
||||
| 'aria-checked'
|
||||
| 'aria-colcount'
|
||||
| 'aria-colindex'
|
||||
| 'aria-colspan'
|
||||
| 'aria-controls'
|
||||
| 'aria-current'
|
||||
| 'aria-describedby'
|
||||
| 'aria-details'
|
||||
| 'aria-disabled'
|
||||
| 'aria-dropeffect'
|
||||
| 'aria-errormessage'
|
||||
| 'aria-expanded'
|
||||
| 'aria-flowto'
|
||||
| 'aria-grabbed'
|
||||
| 'aria-haspopup'
|
||||
| 'aria-hidden'
|
||||
| 'aria-invalid'
|
||||
| 'aria-keyshortcuts'
|
||||
| 'aria-label'
|
||||
| 'aria-labelledby'
|
||||
| 'aria-level'
|
||||
| 'aria-live'
|
||||
| 'aria-modal'
|
||||
| 'aria-multiline'
|
||||
| 'aria-multiselectable'
|
||||
| 'aria-orientation'
|
||||
| 'aria-owns'
|
||||
| 'aria-placeholder'
|
||||
| 'aria-posinset'
|
||||
| 'aria-pressed'
|
||||
| 'aria-readonly'
|
||||
| 'aria-relevant'
|
||||
| 'aria-required'
|
||||
| 'aria-roledescription'
|
||||
| 'aria-rowcount'
|
||||
| 'aria-rowindex'
|
||||
| 'aria-rowspan'
|
||||
| 'aria-selected'
|
||||
| 'aria-setsize'
|
||||
| 'aria-sort'
|
||||
| 'aria-valuemax'
|
||||
| 'aria-valuemin'
|
||||
| 'aria-valuenow'
|
||||
| 'aria-valuetext'
|
||||
| 'readOnly'
|
||||
| 'rows'
|
||||
| 'dangerouslySetInnerHTML'
|
||||
| 'onCopy'
|
||||
| 'onCopyCapture'
|
||||
| 'onCut'
|
||||
| 'onCutCapture'
|
||||
| 'onPaste'
|
||||
| 'onPasteCapture'
|
||||
| 'onCompositionEnd'
|
||||
| 'onCompositionEndCapture'
|
||||
| 'onCompositionStart'
|
||||
| 'onCompositionStartCapture'
|
||||
| 'onCompositionUpdate'
|
||||
| 'onCompositionUpdateCapture'
|
||||
| 'onFocus'
|
||||
| 'onFocusCapture'
|
||||
| 'onBlur'
|
||||
| 'onBlurCapture'
|
||||
| 'onChange'
|
||||
| 'onChangeCapture'
|
||||
| 'onBeforeInput'
|
||||
| 'onBeforeInputCapture'
|
||||
| 'onInput'
|
||||
| 'onInputCapture'
|
||||
| 'onReset'
|
||||
| 'onResetCapture'
|
||||
| 'onSubmit'
|
||||
| 'onSubmitCapture'
|
||||
| 'onInvalid'
|
||||
| 'onInvalidCapture'
|
||||
| 'onLoad'
|
||||
| 'onLoadCapture'
|
||||
| 'onError'
|
||||
| 'onErrorCapture'
|
||||
| 'onKeyDown'
|
||||
| 'onKeyDownCapture'
|
||||
| 'onKeyPress'
|
||||
| 'onKeyPressCapture'
|
||||
| 'onKeyUp'
|
||||
| 'onKeyUpCapture'
|
||||
| 'onAbort'
|
||||
| 'onAbortCapture'
|
||||
| 'onCanPlay'
|
||||
| 'onCanPlayCapture'
|
||||
| 'onCanPlayThrough'
|
||||
| 'onCanPlayThroughCapture'
|
||||
| 'onDurationChange'
|
||||
| 'onDurationChangeCapture'
|
||||
| 'onEmptied'
|
||||
| 'onEmptiedCapture'
|
||||
| 'onEncrypted'
|
||||
| 'onEncryptedCapture'
|
||||
| 'onEnded'
|
||||
| 'onEndedCapture'
|
||||
| 'onLoadedData'
|
||||
| 'onLoadedDataCapture'
|
||||
| 'onLoadedMetadata'
|
||||
| 'onLoadedMetadataCapture'
|
||||
| 'onLoadStart'
|
||||
| 'onLoadStartCapture'
|
||||
| 'onPause'
|
||||
| 'onPauseCapture'
|
||||
| 'onPlay'
|
||||
| 'onPlayCapture'
|
||||
| 'onPlaying'
|
||||
| 'onPlayingCapture'
|
||||
| 'onProgress'
|
||||
| 'onProgressCapture'
|
||||
| 'onRateChange'
|
||||
| 'onRateChangeCapture'
|
||||
| 'onSeeked'
|
||||
| 'onSeekedCapture'
|
||||
| 'onSeeking'
|
||||
| 'onSeekingCapture'
|
||||
| 'onStalled'
|
||||
| 'onStalledCapture'
|
||||
| 'onSuspend'
|
||||
| 'onSuspendCapture'
|
||||
| 'onTimeUpdate'
|
||||
| 'onTimeUpdateCapture'
|
||||
| 'onVolumeChange'
|
||||
| 'onVolumeChangeCapture'
|
||||
| 'onWaiting'
|
||||
| 'onWaitingCapture'
|
||||
| 'onAuxClick'
|
||||
| 'onAuxClickCapture'
|
||||
| 'onClick'
|
||||
| 'onClickCapture'
|
||||
| 'onContextMenu'
|
||||
| 'onContextMenuCapture'
|
||||
| 'onDoubleClick'
|
||||
| 'onDoubleClickCapture'
|
||||
| 'onDrag'
|
||||
| 'onDragCapture'
|
||||
| 'onDragEnd'
|
||||
| 'onDragEndCapture'
|
||||
| 'onDragEnter'
|
||||
| 'onDragEnterCapture'
|
||||
| 'onDragExit'
|
||||
| 'onDragExitCapture'
|
||||
| 'onDragLeave'
|
||||
| 'onDragLeaveCapture'
|
||||
| 'onDragOver'
|
||||
| 'onDragOverCapture'
|
||||
| 'onDragStart'
|
||||
| 'onDragStartCapture'
|
||||
| 'onDrop'
|
||||
| 'onDropCapture'
|
||||
| 'onMouseDown'
|
||||
| 'onMouseDownCapture'
|
||||
| 'onMouseEnter'
|
||||
| 'onMouseLeave'
|
||||
| 'onMouseMove'
|
||||
| 'onMouseMoveCapture'
|
||||
| 'onMouseOut'
|
||||
| 'onMouseOutCapture'
|
||||
| 'onMouseOver'
|
||||
| 'onMouseOverCapture'
|
||||
| 'onMouseUp'
|
||||
| 'onMouseUpCapture'
|
||||
| 'onSelect'
|
||||
| 'onSelectCapture'
|
||||
| 'onTouchCancel'
|
||||
| 'onTouchCancelCapture'
|
||||
| 'onTouchEnd'
|
||||
| 'onTouchEndCapture'
|
||||
| 'onTouchMove'
|
||||
| 'onTouchMoveCapture'
|
||||
| 'onTouchStart'
|
||||
| 'onTouchStartCapture'
|
||||
| 'onPointerDown'
|
||||
| 'onPointerDownCapture'
|
||||
| 'onPointerMove'
|
||||
| 'onPointerMoveCapture'
|
||||
| 'onPointerUp'
|
||||
| 'onPointerUpCapture'
|
||||
| 'onPointerCancel'
|
||||
| 'onPointerCancelCapture'
|
||||
| 'onPointerEnter'
|
||||
| 'onPointerEnterCapture'
|
||||
| 'onPointerLeave'
|
||||
| 'onPointerLeaveCapture'
|
||||
| 'onPointerOver'
|
||||
| 'onPointerOverCapture'
|
||||
| 'onPointerOut'
|
||||
| 'onPointerOutCapture'
|
||||
| 'onGotPointerCapture'
|
||||
| 'onGotPointerCaptureCapture'
|
||||
| 'onLostPointerCapture'
|
||||
| 'onLostPointerCaptureCapture'
|
||||
| 'onScroll'
|
||||
| 'onScrollCapture'
|
||||
| 'onWheel'
|
||||
| 'onWheelCapture'
|
||||
| 'onAnimationStart'
|
||||
| 'onAnimationStartCapture'
|
||||
| 'onAnimationEnd'
|
||||
| 'onAnimationEndCapture'
|
||||
| 'onAnimationIteration'
|
||||
| 'onAnimationIterationCapture'
|
||||
| 'onTransitionEnd'
|
||||
| 'onTransitionEndCapture'
|
||||
| 'classes'
|
||||
| 'innerRef'
|
||||
| 'fullWidth'
|
||||
| 'inputProps'
|
||||
| 'inputRef'
|
||||
| 'multiline'
|
||||
| 'endAdornment'
|
||||
| 'inputComponent'
|
||||
| 'renderSuffix'
|
||||
| 'rowsMax'
|
||||
| 'rowsMin'
|
||||
| 'maxRows'
|
||||
| 'minRows'
|
||||
| 'startAdornment'
|
||||
| 'onClear'
|
||||
| 'debounceTime'
|
||||
| 'clearButton'
|
||||
> &
|
||||
React_2.RefAttributes<unknown>
|
||||
>;
|
||||
export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps>;
|
||||
|
||||
// @public
|
||||
export type SearchBarBaseProps = Omit<InputBaseProps, 'onChange'> & {
|
||||
@@ -724,10 +146,15 @@ export const SearchContextProvider: (
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchContextProviderProps = PropsWithChildren<{
|
||||
initialState?: SearchContextState;
|
||||
useParentContext?: boolean;
|
||||
}>;
|
||||
export type SearchContextProviderProps =
|
||||
| PropsWithChildren<{
|
||||
initialState?: SearchContextState;
|
||||
inheritParentContextIfAvailable?: never;
|
||||
}>
|
||||
| PropsWithChildren<{
|
||||
initialState?: never;
|
||||
inheritParentContextIfAvailable?: boolean;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SearchContextState = {
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
import { Grid, makeStyles, Paper } from '@material-ui/core';
|
||||
import LabelIcon from '@material-ui/icons/Label';
|
||||
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
import { SearchContextProvider } from '../../context';
|
||||
|
||||
import { SearchAutocomplete } from './SearchAutocomplete';
|
||||
import { SearchAutocompleteDefaultOption } from './SearchAutocompleteDefaultOption';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchAutocomplete',
|
||||
component: SearchAutocomplete,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
|
||||
<SearchContextProvider>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
export const Default = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<SearchAutocomplete options={['hello-word', 'petstore', 'spotify']} />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Outlined = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Paper className={classes.root} variant="outlined">
|
||||
<SearchAutocomplete options={['hello-word', 'petstore', 'spotify']} />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Initialized = () => {
|
||||
const classes = useStyles();
|
||||
const options = ['hello-word', 'petstore', 'spotify'];
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<SearchAutocomplete options={options} value={options[0]} />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoadingOptions = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<SearchAutocomplete options={[]} loading />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const RenderingCustomOptions = () => {
|
||||
const classes = useStyles();
|
||||
const options = [
|
||||
{
|
||||
title: 'hello-world',
|
||||
text: 'Hello World example for gRPC',
|
||||
},
|
||||
{
|
||||
title: 'petstore',
|
||||
text: 'The petstore API',
|
||||
},
|
||||
{
|
||||
title: 'spotify',
|
||||
text: 'The Spotify web API',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<SearchAutocomplete
|
||||
options={options}
|
||||
renderOption={option => (
|
||||
<SearchAutocompleteDefaultOption
|
||||
icon={<LabelIcon titleAccess="Option icon" />}
|
||||
primaryText={option.title}
|
||||
secondaryText={option.text}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -25,10 +25,8 @@ import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { TestApiProvider, renderWithEffects } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef } from '../../api';
|
||||
import {
|
||||
SearchAutocomplete,
|
||||
SearchAutocompleteDefaultOption,
|
||||
} from './SearchAutocomplete';
|
||||
import { SearchAutocomplete } from './SearchAutocomplete';
|
||||
import { SearchAutocompleteDefaultOption } from './SearchAutocompleteDefaultOption';
|
||||
|
||||
const title = 'Backstage Test App';
|
||||
const configApiMock = new ConfigReader({
|
||||
@@ -98,7 +96,7 @@ describe('SearchAutocomplete', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(query).toBeCalledWith({
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: options[0],
|
||||
@@ -120,7 +118,7 @@ describe('SearchAutocomplete', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(query).toBeCalledWith({
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: options[0],
|
||||
@@ -131,7 +129,7 @@ describe('SearchAutocomplete', () => {
|
||||
await userEvent.click(screen.getByLabelText('Clear'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(query).toBeCalledWith({
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: '',
|
||||
@@ -157,7 +155,7 @@ describe('SearchAutocomplete', () => {
|
||||
await userEvent.click(screen.getByText(options[0]));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(query).toBeCalledWith({
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: options[0],
|
||||
|
||||
@@ -14,16 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, ReactNode, useCallback, useMemo } from 'react';
|
||||
import React, { ChangeEvent, useCallback, useMemo } from 'react';
|
||||
|
||||
import { CircularProgress } from '@material-ui/core';
|
||||
import {
|
||||
CircularProgress,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
ListItemTextProps,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
Value,
|
||||
Autocomplete,
|
||||
AutocompleteProps,
|
||||
AutocompleteChangeDetails,
|
||||
@@ -39,38 +33,29 @@ import { SearchBar, SearchBarProps } from '../SearchBar';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchAutocompleteProps<
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
> = Omit<
|
||||
AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
'renderInput'
|
||||
export type SearchAutocompleteProps<Option> = Omit<
|
||||
AutocompleteProps<Option, undefined, undefined, boolean>,
|
||||
'renderInput' | 'disableClearable' | 'multiple'
|
||||
> & {
|
||||
'data-testid'?: string;
|
||||
inputPlaceholder?: SearchBarProps['placeholder'];
|
||||
inputDebounceTime?: SearchBarProps['debounceTime'];
|
||||
renderInput?: (params: AutocompleteRenderInputParams) => JSX.Element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for {@link SearchAutocomplete}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchAutocompleteComponent = <Option>(
|
||||
props: SearchAutocompleteProps<Option>,
|
||||
) => JSX.Element;
|
||||
|
||||
const withContext = (
|
||||
Component: <
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
>(
|
||||
props: SearchAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
) => JSX.Element,
|
||||
) => {
|
||||
return <
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
>(
|
||||
props: SearchAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
) => (
|
||||
<SearchContextProvider useParentContext>
|
||||
Component: SearchAutocompleteComponent,
|
||||
): SearchAutocompleteComponent => {
|
||||
return props => (
|
||||
<SearchContextProvider inheritParentContextIfAvailable>
|
||||
<Component {...props} />
|
||||
</SearchContextProvider>
|
||||
);
|
||||
@@ -82,43 +67,54 @@ const withContext = (
|
||||
* @public
|
||||
*/
|
||||
export const SearchAutocomplete = withContext(
|
||||
<
|
||||
T,
|
||||
Multiple extends boolean | undefined,
|
||||
DisableClearable extends boolean | undefined,
|
||||
FreeSolo extends boolean | undefined,
|
||||
>({
|
||||
loading,
|
||||
value,
|
||||
onChange = () => {},
|
||||
options = [],
|
||||
getOptionLabel = (option: T) => String(option),
|
||||
renderInput,
|
||||
inputDebounceTime,
|
||||
fullWidth = true,
|
||||
clearOnBlur = false,
|
||||
...rest
|
||||
}: SearchAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>) => {
|
||||
function SearchAutocompleteComponent<Option>(
|
||||
props: SearchAutocompleteProps<Option>,
|
||||
) {
|
||||
const {
|
||||
loading,
|
||||
value,
|
||||
onChange = () => {},
|
||||
options = [],
|
||||
getOptionLabel = (option: Option) => String(option),
|
||||
inputPlaceholder,
|
||||
inputDebounceTime,
|
||||
freeSolo = true,
|
||||
fullWidth = true,
|
||||
clearOnBlur = false,
|
||||
'data-testid': dataTestId = 'search-autocomplete',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { setTerm } = useSearch();
|
||||
|
||||
const inputValue = useMemo(() => {
|
||||
return value ? getOptionLabel(value as T) : '';
|
||||
}, [value, getOptionLabel]);
|
||||
const getInputValue = useCallback(
|
||||
(option?: null | string | Option) => {
|
||||
if (!option) return '';
|
||||
if (typeof option === 'string') return option;
|
||||
return getOptionLabel(option);
|
||||
},
|
||||
[getOptionLabel],
|
||||
);
|
||||
|
||||
const inputValue = useMemo(
|
||||
() => getInputValue(value),
|
||||
[value, getInputValue],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(
|
||||
event: ChangeEvent<{}>,
|
||||
newValue: Value<T, Multiple, DisableClearable, FreeSolo>,
|
||||
option: null | string | Option,
|
||||
reason: AutocompleteChangeReason,
|
||||
details?: AutocompleteChangeDetails<T>,
|
||||
details?: AutocompleteChangeDetails<Option>,
|
||||
) => {
|
||||
onChange(event, newValue, reason, details);
|
||||
setTerm(newValue ? getOptionLabel(newValue as T) : '');
|
||||
setTerm(getInputValue(option));
|
||||
onChange(event, option, reason, details);
|
||||
},
|
||||
[getOptionLabel, setTerm, onChange],
|
||||
[getInputValue, setTerm, onChange],
|
||||
);
|
||||
|
||||
const defaultRenderInput = useCallback(
|
||||
const renderInput = useCallback(
|
||||
({
|
||||
InputProps: { ref, endAdornment },
|
||||
InputLabelProps,
|
||||
@@ -129,6 +125,7 @@ export const SearchAutocomplete = withContext(
|
||||
ref={ref}
|
||||
clearButton={false}
|
||||
value={inputValue}
|
||||
placeholder={inputPlaceholder}
|
||||
debounceTime={inputDebounceTime}
|
||||
endAdornment={
|
||||
loading ? (
|
||||
@@ -143,60 +140,22 @@ export const SearchAutocomplete = withContext(
|
||||
}
|
||||
/>
|
||||
),
|
||||
[loading, inputValue, inputDebounceTime],
|
||||
[loading, inputValue, inputPlaceholder, inputDebounceTime],
|
||||
);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
data-testid="search-autocomplete"
|
||||
{...rest}
|
||||
data-testid={dataTestId}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
getOptionLabel={getOptionLabel}
|
||||
renderInput={renderInput ?? defaultRenderInput}
|
||||
renderInput={renderInput}
|
||||
freeSolo={freeSolo}
|
||||
fullWidth={fullWidth}
|
||||
clearOnBlur={clearOnBlur}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for {@link SearchAutocompleteDefaultOption}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchAutocompleteDefaultOptionProps = {
|
||||
icon?: ReactNode;
|
||||
primaryText: ListItemTextProps['primary'];
|
||||
primaryTextTypographyProps?: ListItemTextProps['primaryTypographyProps'];
|
||||
secondaryText?: ListItemTextProps['secondary'];
|
||||
secondaryTextTypographyProps?: ListItemTextProps['secondaryTypographyProps'];
|
||||
disableTextTypography?: ListItemTextProps['disableTypography'];
|
||||
};
|
||||
|
||||
/**
|
||||
* A default search bar autocomplete component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchAutocompleteDefaultOption = ({
|
||||
icon,
|
||||
primaryText,
|
||||
primaryTextTypographyProps,
|
||||
secondaryText,
|
||||
secondaryTextTypographyProps,
|
||||
disableTextTypography,
|
||||
}: SearchAutocompleteDefaultOptionProps) => (
|
||||
<>
|
||||
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
|
||||
<ListItemText
|
||||
primary={primaryText}
|
||||
primaryTypographyProps={primaryTextTypographyProps}
|
||||
secondary={secondaryText}
|
||||
secondaryTypographyProps={secondaryTextTypographyProps}
|
||||
disableTypography={disableTextTypography}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType, PropsWithChildren } from 'react';
|
||||
|
||||
import { Grid, ListItem } from '@material-ui/core';
|
||||
import LabelIcon from '@material-ui/icons/Label';
|
||||
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
import { SearchContextProvider } from '../../context';
|
||||
|
||||
import { SearchAutocompleteDefaultOption } from './SearchAutocompleteDefaultOption';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchAutocompleteDefaultOption',
|
||||
component: SearchAutocompleteDefaultOption,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
|
||||
<SearchContextProvider>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<ListItem>
|
||||
<Story />
|
||||
</ListItem>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<SearchAutocompleteDefaultOption primaryText="hello-world" />
|
||||
);
|
||||
|
||||
export const Icon = () => (
|
||||
<SearchAutocompleteDefaultOption
|
||||
icon={<LabelIcon />}
|
||||
primaryText="hello-world"
|
||||
/>
|
||||
);
|
||||
|
||||
export const SecondaryText = () => (
|
||||
<SearchAutocompleteDefaultOption
|
||||
primaryText="hello-world"
|
||||
secondaryText="Hello World example for gRPC"
|
||||
/>
|
||||
);
|
||||
|
||||
export const AllCombined = () => (
|
||||
<SearchAutocompleteDefaultOption
|
||||
icon={<LabelIcon />}
|
||||
primaryText="hello-world"
|
||||
secondaryText="Hello World example for gRPC"
|
||||
/>
|
||||
);
|
||||
|
||||
export const CustomTextTypographies = () => (
|
||||
<SearchAutocompleteDefaultOption
|
||||
icon={<LabelIcon />}
|
||||
primaryText="hello-world"
|
||||
primaryTextTypographyProps={{ color: 'primary' }}
|
||||
secondaryText="Hello World example for gRPC"
|
||||
secondaryTextTypographyProps={{ color: 'secondary' }}
|
||||
/>
|
||||
);
|
||||
|
||||
const CustomPrimaryText = ({ children }: PropsWithChildren<{}>) => (
|
||||
<dt>{children}</dt>
|
||||
);
|
||||
|
||||
const CustomSecondaryText = ({ children }: PropsWithChildren<{}>) => (
|
||||
<dd>{children}</dd>
|
||||
);
|
||||
|
||||
export const CustomTextComponents = () => (
|
||||
<dl>
|
||||
<SearchAutocompleteDefaultOption
|
||||
icon={<LabelIcon />}
|
||||
primaryText={<CustomPrimaryText>hello-world</CustomPrimaryText>}
|
||||
secondaryText={
|
||||
<CustomSecondaryText>Hello World example for gRPC</CustomSecondaryText>
|
||||
}
|
||||
disableTextTypography
|
||||
/>
|
||||
</dl>
|
||||
);
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
ListItemTextProps,
|
||||
} from '@material-ui/core';
|
||||
|
||||
/**
|
||||
* Props for {@link SearchAutocompleteDefaultOption}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchAutocompleteDefaultOptionProps = {
|
||||
icon?: ReactNode;
|
||||
primaryText: ListItemTextProps['primary'];
|
||||
primaryTextTypographyProps?: ListItemTextProps['primaryTypographyProps'];
|
||||
secondaryText?: ListItemTextProps['secondary'];
|
||||
secondaryTextTypographyProps?: ListItemTextProps['secondaryTypographyProps'];
|
||||
disableTextTypography?: ListItemTextProps['disableTypography'];
|
||||
};
|
||||
|
||||
/**
|
||||
* A default search autocomplete option component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchAutocompleteDefaultOption = ({
|
||||
icon,
|
||||
primaryText,
|
||||
primaryTextTypographyProps,
|
||||
secondaryText,
|
||||
secondaryTextTypographyProps,
|
||||
disableTextTypography,
|
||||
}: SearchAutocompleteDefaultOptionProps) => (
|
||||
<>
|
||||
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
|
||||
<ListItemText
|
||||
primary={primaryText}
|
||||
primaryTypographyProps={primaryTextTypographyProps}
|
||||
secondary={secondaryText}
|
||||
secondaryTypographyProps={secondaryTextTypographyProps}
|
||||
disableTypography={disableTextTypography}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
SearchAutocomplete,
|
||||
SearchAutocompleteDefaultOption,
|
||||
} from './SearchAutocomplete';
|
||||
export { SearchAutocomplete } from './SearchAutocomplete';
|
||||
|
||||
export { SearchAutocompleteDefaultOption } from './SearchAutocompleteDefaultOption';
|
||||
|
||||
export type {
|
||||
SearchAutocompleteProps,
|
||||
SearchAutocompleteDefaultOptionProps,
|
||||
SearchAutocompleteComponent,
|
||||
} from './SearchAutocomplete';
|
||||
|
||||
export type { SearchAutocompleteDefaultOptionProps } from './SearchAutocompleteDefaultOption';
|
||||
|
||||
@@ -22,6 +22,7 @@ import React, {
|
||||
useCallback,
|
||||
forwardRef,
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
} from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
|
||||
@@ -43,6 +44,14 @@ import {
|
||||
import { SearchContextProvider, useSearch } from '../../context';
|
||||
import { TrackSearch } from '../SearchTracker';
|
||||
|
||||
function withContext<T>(Component: ComponentType<T>) {
|
||||
return forwardRef<unknown, T>((props, ref) => (
|
||||
<SearchContextProvider inheritParentContextIfAvailable>
|
||||
<Component {...props} ref={ref} />
|
||||
</SearchContextProvider>
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for {@link SearchBarBase}.
|
||||
*
|
||||
@@ -63,97 +72,98 @@ export type SearchBarBaseProps = Omit<InputBaseProps, 'onChange'> & {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchBarBase = forwardRef<unknown, SearchBarBaseProps>(
|
||||
(
|
||||
{
|
||||
onChange,
|
||||
onKeyDown = () => {},
|
||||
onClear = () => {},
|
||||
onSubmit = () => {},
|
||||
debounceTime = 200,
|
||||
clearButton = true,
|
||||
fullWidth = true,
|
||||
value: defaultValue,
|
||||
inputProps: defaultInputProps = {},
|
||||
endAdornment: defaultEndAdornment,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const [value, setValue] = useState<string>('');
|
||||
export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps> =
|
||||
withContext(
|
||||
forwardRef((props, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown = () => {},
|
||||
onClear = () => {},
|
||||
onSubmit = () => {},
|
||||
debounceTime = 200,
|
||||
clearButton = true,
|
||||
fullWidth = true,
|
||||
value: defaultValue,
|
||||
placeholder: defaultPlaceholder,
|
||||
inputProps: defaultInputProps = {},
|
||||
endAdornment: defaultEndAdornment,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
setValue(prevValue =>
|
||||
prevValue !== defaultValue ? String(defaultValue) : prevValue,
|
||||
const configApi = useApi(configApiRef);
|
||||
const [value, setValue] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setValue(prevValue =>
|
||||
prevValue !== defaultValue ? String(defaultValue) : prevValue,
|
||||
);
|
||||
}, [defaultValue]);
|
||||
|
||||
useDebounce(() => onChange(value), debounceTime, [value]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
}, [defaultValue]);
|
||||
|
||||
useDebounce(() => onChange(value), debounceTime, [value]);
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (onKeyDown) onKeyDown(e);
|
||||
if (onSubmit && e.key === 'Enter') {
|
||||
onSubmit();
|
||||
}
|
||||
},
|
||||
[onKeyDown, onSubmit],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (onKeyDown) onKeyDown(e);
|
||||
if (onSubmit && e.key === 'Enter') {
|
||||
onSubmit();
|
||||
const handleClear = useCallback(() => {
|
||||
onChange('');
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
},
|
||||
[onKeyDown, onSubmit],
|
||||
);
|
||||
}, [onChange, onClear]);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
onChange('');
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
}, [onChange, onClear]);
|
||||
const placeholder =
|
||||
defaultPlaceholder ??
|
||||
`Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;
|
||||
|
||||
const placeholder = `Search in ${
|
||||
configApi.getOptionalString('app.title') || 'Backstage'
|
||||
}`;
|
||||
const startAdornment = (
|
||||
<InputAdornment position="start">
|
||||
<IconButton aria-label="Query" size="small" disabled>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
const startAdornment = (
|
||||
<InputAdornment position="start">
|
||||
<IconButton aria-label="Query" size="small" disabled>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
const endAdornment = (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label="Clear" size="small" onClick={handleClear}>
|
||||
<ClearButton />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
const endAdornment = (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label="Clear" size="small" onClick={handleClear}>
|
||||
<ClearButton />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
return (
|
||||
<TrackSearch>
|
||||
<InputBase
|
||||
data-testid="search-bar-next"
|
||||
ref={ref}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
startAdornment={startAdornment}
|
||||
endAdornment={clearButton ? endAdornment : defaultEndAdornment}
|
||||
inputProps={{ 'aria-label': 'Search', ...defaultInputProps }}
|
||||
fullWidth={fullWidth}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...props}
|
||||
/>
|
||||
</TrackSearch>
|
||||
);
|
||||
},
|
||||
);
|
||||
return (
|
||||
<TrackSearch>
|
||||
<InputBase
|
||||
data-testid="search-bar-next"
|
||||
ref={ref}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
startAdornment={startAdornment}
|
||||
endAdornment={clearButton ? endAdornment : defaultEndAdornment}
|
||||
inputProps={{ 'aria-label': 'Search', ...defaultInputProps }}
|
||||
fullWidth={fullWidth}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
</TrackSearch>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for {@link SearchBar}.
|
||||
@@ -162,53 +172,45 @@ export const SearchBarBase = forwardRef<unknown, SearchBarBaseProps>(
|
||||
*/
|
||||
export type SearchBarProps = Partial<SearchBarBaseProps>;
|
||||
|
||||
const withContext = (Component: ComponentType<SearchBarProps>) => {
|
||||
return forwardRef<unknown, SearchBarProps>((props, ref) => (
|
||||
<SearchContextProvider useParentContext>
|
||||
<Component {...props} ref={ref} />
|
||||
</SearchContextProvider>
|
||||
));
|
||||
};
|
||||
|
||||
/**
|
||||
* Recommended search bar when you use the Search Provider or Search Context.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchBar = withContext(
|
||||
forwardRef<unknown, SearchBarProps>(
|
||||
({ value: initialValue = '', onChange, ...rest }, ref) => {
|
||||
const { term, setTerm } = useSearch();
|
||||
export const SearchBar: ForwardRefExoticComponent<SearchBarProps> = withContext(
|
||||
forwardRef((props, ref) => {
|
||||
const { value: initialValue = '', onChange, ...rest } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValue) {
|
||||
setTerm(String(initialValue));
|
||||
const { term, setTerm } = useSearch();
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValue) {
|
||||
setTerm(String(initialValue));
|
||||
}
|
||||
}, [initialValue, setTerm]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newValue: string) => {
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setTerm(newValue);
|
||||
}
|
||||
}, [initialValue, setTerm]);
|
||||
},
|
||||
[onChange, setTerm],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newValue: string) => {
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setTerm(newValue);
|
||||
}
|
||||
},
|
||||
[onChange, setTerm],
|
||||
);
|
||||
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{ pluginId: 'search', extension: 'SearchBar' }}
|
||||
>
|
||||
<SearchBarBase
|
||||
{...rest}
|
||||
ref={ref}
|
||||
value={term}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
},
|
||||
),
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{ pluginId: 'search', extension: 'SearchBar' }}
|
||||
>
|
||||
<SearchBarBase
|
||||
{...rest}
|
||||
ref={ref}
|
||||
value={term}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -100,11 +100,6 @@ const searchInitialState: SearchContextState = {
|
||||
types: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new local search context.
|
||||
* @remarks Use it for isolating this context from parent search contexts.
|
||||
* @internal
|
||||
*/
|
||||
const useSearchContextValue = (
|
||||
initialValue: SearchContextState = searchInitialState,
|
||||
) => {
|
||||
@@ -165,32 +160,15 @@ const useSearchContextValue = (
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchContextProvider}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchContextProviderProps = PropsWithChildren<{
|
||||
export type LocalSearchContextProps = PropsWithChildren<{
|
||||
initialState?: SearchContextState;
|
||||
/**
|
||||
* If true, don't create a child context if there is a parent one already defined.
|
||||
* @remarks Default to false.
|
||||
*/
|
||||
useParentContext?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Search context provider which gives you access to shared state between search components
|
||||
*/
|
||||
export const SearchContextProvider = (props: SearchContextProviderProps) => {
|
||||
const { initialState, useParentContext, children } = props;
|
||||
const hasParentContext = useSearchContextCheck();
|
||||
const LocalSearchContext = (props: SearchContextProviderProps) => {
|
||||
const { initialState, children } = props;
|
||||
const value = useSearchContextValue(initialState);
|
||||
|
||||
return useParentContext && hasParentContext ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{ searchTypes: value.types.sort().join(',') }}
|
||||
>
|
||||
@@ -200,3 +178,48 @@ export const SearchContextProvider = (props: SearchContextProviderProps) => {
|
||||
</AnalyticsContext>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchContextProvider}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchContextProviderProps =
|
||||
| PropsWithChildren<{
|
||||
/**
|
||||
* State initialized by a local context.
|
||||
*/
|
||||
initialState?: SearchContextState;
|
||||
/**
|
||||
* Do not create an inheritance from the parent, as a new initial state must be defined in a local context.
|
||||
*/
|
||||
inheritParentContextIfAvailable?: never;
|
||||
}>
|
||||
| PropsWithChildren<{
|
||||
/**
|
||||
* Does not accept initial state since it is already initialized by parent context.
|
||||
*/
|
||||
initialState?: never;
|
||||
/**
|
||||
* If true, don't create a child context if there is a parent one already defined.
|
||||
* @remarks Defaults to false.
|
||||
*/
|
||||
inheritParentContextIfAvailable?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Search context provider which gives you access to shared state between search components
|
||||
*/
|
||||
export const SearchContextProvider = (props: SearchContextProviderProps) => {
|
||||
const { initialState, inheritParentContextIfAvailable, children } = props;
|
||||
const hasParentContext = useSearchContextCheck();
|
||||
|
||||
return hasParentContext && inheritParentContextIfAvailable ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<LocalSearchContext initialState={initialState}>
|
||||
{children}
|
||||
</LocalSearchContext>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user