Move SearchModal state to useSearchModal
Signed-off-by: Vincenzo Scamporlino <me@vinzscam.dev>
This commit is contained in:
@@ -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} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -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 />
|
||||
...
|
||||
```
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user