Introduce SearchModalProvider to allow modal to be controlled externally
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search': patch
|
||||
---
|
||||
|
||||
Introduced a `<SearchModalProvider>`, which can optionally be placed higher up in the react tree in order to allow control of search modal visibility from outside the modal itself.
|
||||
@@ -195,6 +195,28 @@ export interface SearchModalProps {
|
||||
toggleModal: () => void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const SearchModalProvider: ({
|
||||
children,
|
||||
showInitially,
|
||||
}: SearchModalProviderProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchModalProviderProps = {
|
||||
children: ReactNode;
|
||||
showInitially?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SearchModalValue = {
|
||||
state: {
|
||||
hidden: boolean;
|
||||
open: boolean;
|
||||
};
|
||||
toggleModal: () => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -297,12 +319,5 @@ export type SidebarSearchProps = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export function useSearchModal(initialState?: boolean): {
|
||||
state: {
|
||||
hidden: boolean;
|
||||
open: boolean;
|
||||
};
|
||||
toggleModal: () => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
export function useSearchModal(initialState?: boolean): SearchModalValue;
|
||||
```
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@backstage/plugin-search-react": "^0.2.0-next.2",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@backstage/version-bridge": "^1.0.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
|
||||
@@ -15,4 +15,8 @@
|
||||
*/
|
||||
export { SearchModal } from './SearchModal';
|
||||
export type { SearchModalChildrenProps, SearchModalProps } from './SearchModal';
|
||||
export { useSearchModal } from './useSearchModal';
|
||||
export { SearchModalProvider, useSearchModal } from './useSearchModal';
|
||||
export type {
|
||||
SearchModalProviderProps,
|
||||
SearchModalValue,
|
||||
} from './useSearchModal';
|
||||
|
||||
@@ -14,7 +14,88 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import React, { ReactNode, useCallback, useContext, useState } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
|
||||
/**
|
||||
* The state of the search modal, as well as functions for changing the modal's
|
||||
* visibility.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchModalValue = {
|
||||
state: {
|
||||
hidden: boolean;
|
||||
open: boolean;
|
||||
};
|
||||
toggleModal: () => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const SearchModalContext = createVersionedContext<{
|
||||
1: SearchModalValue | undefined;
|
||||
}>('analytics-context');
|
||||
|
||||
/**
|
||||
* Props for the SearchModalProvider.
|
||||
* @public
|
||||
*/
|
||||
export type SearchModalProviderProps = {
|
||||
/**
|
||||
* Children which should have access to the SearchModal context and the
|
||||
* associated useSearchModal() hook.
|
||||
*/
|
||||
children: ReactNode;
|
||||
|
||||
/**
|
||||
* Pass true if the modal should be rendered initially.
|
||||
*/
|
||||
showInitially?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A context provider responsible for storing and managing state related to the
|
||||
* search modal.
|
||||
*
|
||||
* @remarks
|
||||
* If you need to control visibility of the search toggle outside of the modal
|
||||
* itself, you can optionally place this higher up in the react tree where your
|
||||
* custom code and the search modal share the same context.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import {
|
||||
* SearchModalProvider,
|
||||
* SidebarSearchModal,
|
||||
* } from '@backstage/plugin-search';
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* <SearchModalProvider>
|
||||
* <KeyboardShortcutSearchToggler />
|
||||
* <SidebarSearchModal>
|
||||
* {({ toggleModal }) => <SearchModal toggleModal={toggleModal} />}
|
||||
* </SidebarSearchModal>
|
||||
* </SearchModalProvider>
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SearchModalProvider = ({
|
||||
children,
|
||||
showInitially,
|
||||
}: SearchModalProviderProps) => {
|
||||
const value = useSearchModal(showInitially);
|
||||
const versionedValue = createVersionedValueMap({ 1: value });
|
||||
return (
|
||||
<SearchModalContext.Provider value={versionedValue}>
|
||||
{children}
|
||||
</SearchModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this hook to manage the state of {@link SearchModal}
|
||||
@@ -27,6 +108,10 @@ import { useCallback, useState } from 'react';
|
||||
* functions for changing the visibility of the modal.
|
||||
*/
|
||||
export function useSearchModal(initialState = false) {
|
||||
// Check for any existing parent context.
|
||||
const parentContext = useContext(SearchModalContext);
|
||||
const parentContextValue = parentContext?.atVersion(1);
|
||||
|
||||
const [state, setState] = useState({
|
||||
hidden: !initialState,
|
||||
open: initialState,
|
||||
@@ -50,5 +135,8 @@ export function useSearchModal(initialState = false) {
|
||||
[],
|
||||
);
|
||||
|
||||
return { state, toggleModal, setOpen };
|
||||
// Inherit from parent context, if set.
|
||||
return parentContextValue
|
||||
? parentContextValue
|
||||
: { state, toggleModal, setOpen };
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
SearchModal,
|
||||
SearchModalChildrenProps,
|
||||
SearchModalProvider,
|
||||
useSearchModal,
|
||||
} from '../SearchModal';
|
||||
|
||||
@@ -28,7 +29,7 @@ export type SidebarSearchModalProps = {
|
||||
children?: (props: SearchModalChildrenProps) => JSX.Element;
|
||||
};
|
||||
|
||||
export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
|
||||
const SidebarSearchModalContent = (props: SidebarSearchModalProps) => {
|
||||
const { state, toggleModal } = useSearchModal();
|
||||
const Icon = props.icon ? props.icon : SearchIcon;
|
||||
|
||||
@@ -48,3 +49,11 @@ export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
|
||||
return (
|
||||
<SearchModalProvider>
|
||||
<SidebarSearchModalContent {...props} />
|
||||
</SearchModalProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,10 +34,16 @@ export type {
|
||||
SearchFilterComponentProps,
|
||||
SearchFilterWrapperProps,
|
||||
} from './components/SearchFilter';
|
||||
export { SearchModal, useSearchModal } from './components/SearchModal';
|
||||
export {
|
||||
SearchModal,
|
||||
SearchModalProvider,
|
||||
useSearchModal,
|
||||
} from './components/SearchModal';
|
||||
export type {
|
||||
SearchModalChildrenProps,
|
||||
SearchModalProps,
|
||||
SearchModalProviderProps,
|
||||
SearchModalValue,
|
||||
} from './components/SearchModal';
|
||||
export { SearchPage as Router } from './components/SearchPage';
|
||||
export { SearchResultPager } from './components/SearchResultPager';
|
||||
|
||||
Reference in New Issue
Block a user