Introduce SearchModalProvider to allow modal to be controlled externally

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2022-05-16 15:20:18 +02:00
parent f3d0821bad
commit bef56488ad
7 changed files with 141 additions and 13 deletions
+5
View File
@@ -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.
+23 -8
View File
@@ -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;
```
+1
View File
@@ -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>
);
};
+7 -1
View File
@@ -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';