Move SearchModal state to useSearchModal

Signed-off-by: Vincenzo Scamporlino <me@vinzscam.dev>
This commit is contained in:
Vincenzo Scamporlino
2022-02-09 17:48:00 +01:00
parent f903743142
commit 52d2d36b4b
9 changed files with 163 additions and 38 deletions
+15 -2
View File
@@ -1,5 +1,18 @@
---
'@backstage/plugin-search': patch
'@backstage/plugin-search': minor
---
SearchModal invoke search only when visible
**BREAKING**: `useSearch` doesn't return anymore `open` and `toggleModal`.
The two properties have been moved to the `useSearchModal` hook.
```
import { SearchModal, useSearchModal } from '@backstage/plugin-search';
const Foo = () => {
const { state, setOpen, toggleModal } = useSearchModal();
return (
<SearchModal {...state} toggleModal={toggleModal} />
);
};
```
+2 -4
View File
@@ -114,15 +114,13 @@ const routes = (
In `Root.tsx`, add the `SidebarSearchModal` component:
```bash
import { SidebarSearchModal, SearchContextProvider } from '@backstage/plugin-search';
import { SidebarSearchModal } from '@backstage/plugin-search';
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarLogo />
<SearchContextProvider>
<SidebarSearchModal />
</SearchContextProvider>
<SidebarSearchModal />
<SidebarDivider />
...
```
+10
View File
@@ -327,4 +327,14 @@ export type SidebarSearchProps = {
//
// @public (undocumented)
export const useSearch: () => SearchContextValue;
// @public
export function useSearchModal(initialState?: boolean): {
state: {
hidden: boolean;
open: boolean;
};
toggleModal: () => void;
setOpen: (open: boolean) => void;
};
```
@@ -16,10 +16,11 @@
import { wrapInTestApp } from '@backstage/test-utils';
import { Button } from '@material-ui/core';
import React, { ComponentType, useState } from 'react';
import React, { ComponentType } from 'react';
import { rootRouteRef } from '../../plugin';
import { SearchApiProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchModal } from './SearchModal';
import { useSearchModal } from './useSearchModal';
const mockResults = {
results: [
@@ -65,23 +66,14 @@ export default {
};
export const Default = () => {
const [state, setState] = useState({ hidden: true, opened: false });
const handleToggleModal = () =>
setState(previousState => ({
opened: true,
hidden: !previousState.hidden,
}));
const { state, toggleModal } = useSearchModal();
return (
<>
<Button variant="contained" color="primary" onClick={handleToggleModal}>
<Button variant="contained" color="primary" onClick={toggleModal}>
Toggle Search Modal
</Button>
<SearchModal
open={state.opened}
hidden={state.hidden}
toggleModal={handleToggleModal}
/>
<SearchModal {...state} toggleModal={toggleModal} />
</>
);
};
@@ -15,3 +15,4 @@
*/
export { SearchModal } from './SearchModal';
export type { SearchModalProps } from './SearchModal';
export { useSearchModal } from './useSearchModal';
@@ -0,0 +1,70 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { useSearchModal } from './useSearchModal';
describe('useSearchModal', () => {
it.each([
[true, { open: true, hidden: false }],
[false, { open: false, hidden: true }],
])(
'should return the correct state when initial state is %s',
(initialState, result) => {
const rendered = renderHook(() => useSearchModal(initialState));
expect(rendered.result.current.state).toEqual(result);
},
);
it('should properly toggle the state', () => {
const rendered = renderHook(() => useSearchModal());
act(() => rendered.result.current.toggleModal());
expect(rendered.result.current.state).toEqual({
open: true,
hidden: false,
});
act(() => rendered.result.current.toggleModal());
expect(rendered.result.current.state).toEqual({
open: true,
hidden: true,
});
});
it('should properly change the state', () => {
const rendered = renderHook(() => useSearchModal());
act(() => rendered.result.current.setOpen(false));
expect(rendered.result.current.state).toEqual({
open: false,
hidden: true,
});
act(() => rendered.result.current.setOpen(true));
expect(rendered.result.current.state).toEqual({
open: true,
hidden: false,
});
act(() => rendered.result.current.setOpen(false));
expect(rendered.result.current.state).toEqual({
open: true,
hidden: true,
});
});
});
@@ -0,0 +1,54 @@
/*
* 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 { useCallback, useState } from 'react';
/**
* Use this hook to manage the state of {@link SearchModal}
* and change its visibility.
*
* @public
*
* @param initialState - pass `true` to make the modal initially visible
* @returns an object containing the state of the modal together with
* functions for changing the visibility of the modal.
*/
export function useSearchModal(initialState = false) {
const [state, setState] = useState({
hidden: !initialState,
open: initialState,
});
const toggleModal = useCallback(
() =>
setState(prevState => ({
open: true,
hidden: !prevState.hidden,
})),
[],
);
const setOpen = useCallback(
(open: boolean) =>
setState(prevState => ({
open: prevState.open || open,
hidden: !open,
})),
[],
);
return { state, toggleModal, setOpen };
}
@@ -13,42 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useCallback, useState } from 'react';
import React from 'react';
import SearchIcon from '@material-ui/icons/Search';
import { SidebarItem } from '@backstage/core-components';
import { IconComponent } from '@backstage/core-plugin-api';
import { SearchModal } from '../SearchModal';
import { SearchModal, useSearchModal } from '../SearchModal';
export type SidebarSearchModalProps = {
icon?: IconComponent;
};
export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
const [state, setState] = useState({ hidden: true, opened: false });
const { state, toggleModal } = useSearchModal();
const Icon = props.icon ? props.icon : SearchIcon;
const handleToggleModal = useCallback(
() =>
setState(previousState => ({
opened: true,
hidden: !previousState.hidden,
})),
[],
);
return (
<>
<SidebarItem
className="search-icon"
icon={Icon}
text="Search"
onClick={handleToggleModal}
/>
<SearchModal
open={state.opened}
hidden={state.hidden}
toggleModal={handleToggleModal}
onClick={toggleModal}
/>
<SearchModal {...state} toggleModal={toggleModal} />
</>
);
};
+1 -1
View File
@@ -39,7 +39,7 @@ export type {
SearchFilterComponentProps,
SearchFilterWrapperProps,
} from './components/SearchFilter';
export { SearchModal } from './components/SearchModal';
export { SearchModal, useSearchModal } from './components/SearchModal';
export type { SearchModalProps } from './components/SearchModal';
export { SearchPage as Router } from './components/SearchPage';
export { SearchResultPager } from './components/SearchResultPager';