refactor(search): remove search modal for now
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -30,7 +30,6 @@ import {
|
||||
sidebarConfig,
|
||||
SidebarDivider,
|
||||
SidebarItem,
|
||||
SidebarGroup,
|
||||
} from '@backstage/core-components';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import LogoIcon from '../../../app/src/components/Root/LogoIcon';
|
||||
@@ -66,15 +65,10 @@ const SidebarLogo = () => {
|
||||
};
|
||||
|
||||
const SidebarNavItem = (props: NavTarget) => {
|
||||
const { icon: Icon, title, group, routeRef } = props;
|
||||
const { icon: Icon, title, routeRef } = props;
|
||||
const to = useRouteRef(routeRef)();
|
||||
return group ? (
|
||||
<SidebarGroup to={to} icon={<Icon />} label={title}>
|
||||
{group}
|
||||
</SidebarGroup>
|
||||
) : (
|
||||
<SidebarItem to={to} icon={Icon} text={title} />
|
||||
);
|
||||
// TODO: Support opening modal, for example, the search one
|
||||
return <SidebarItem to={to} icon={Icon} text={title} />;
|
||||
};
|
||||
|
||||
export const CoreNav = createExtension({
|
||||
|
||||
@@ -16,33 +16,19 @@
|
||||
|
||||
import { IconComponent, RouteRef } from '@backstage/core-plugin-api';
|
||||
import { createSchemaFromZod } from '../schema/createSchemaFromZod';
|
||||
import {
|
||||
AnyExtensionInputMap,
|
||||
ExtensionInputValues,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
} from '../wiring';
|
||||
import { Expand } from '../wiring/createExtension';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import React from 'react';
|
||||
import { coreExtensionData, createExtension } from '../wiring';
|
||||
|
||||
/**
|
||||
* Helper for creating extensions for a nav item.
|
||||
* @public
|
||||
*/
|
||||
export function createNavItemExtension<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
>(options: {
|
||||
export function createNavItemExtension(options: {
|
||||
id: string;
|
||||
routeRef: RouteRef;
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
inputs?: TInputs;
|
||||
group?: (options: {
|
||||
inputs: Expand<ExtensionInputValues<TInputs>>;
|
||||
}) => Promise<JSX.Element>;
|
||||
}) {
|
||||
const { id, routeRef, title, icon, group } = options;
|
||||
const { id, routeRef, title, icon } = options;
|
||||
return createExtension({
|
||||
id,
|
||||
at: 'core.nav/items',
|
||||
@@ -51,28 +37,15 @@ export function createNavItemExtension<
|
||||
title: z.string().default(title),
|
||||
}),
|
||||
),
|
||||
inputs: options.inputs,
|
||||
output: {
|
||||
navTarget: coreExtensionData.navTarget,
|
||||
},
|
||||
factory: ({ bind, config, inputs }) => {
|
||||
const Group = group
|
||||
? (lazy(() =>
|
||||
group({ inputs }).then(element => ({
|
||||
default: () => element,
|
||||
})),
|
||||
) as unknown as () => JSX.Element)
|
||||
: undefined;
|
||||
factory: ({ bind, config }) => {
|
||||
bind({
|
||||
navTarget: {
|
||||
title: config.title,
|
||||
icon,
|
||||
routeRef,
|
||||
group: Group ? (
|
||||
<Suspense fallback="...">
|
||||
<Group />
|
||||
</Suspense>
|
||||
) : undefined,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -27,7 +27,6 @@ export type NavTarget = {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<{}>;
|
||||
group?: JSX.Element;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -13,9 +13,6 @@ export default _default;
|
||||
// @alpha (undocumented)
|
||||
export const SearchApi: Extension<{}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const SearchModal: Extension<never>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const SearchNavItem: Extension<{
|
||||
title: string;
|
||||
@@ -24,6 +21,7 @@ export const SearchNavItem: Extension<{
|
||||
// @alpha (undocumented)
|
||||
export const SearchPage: Extension<{
|
||||
path: string;
|
||||
noTrack: boolean;
|
||||
}>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -14,24 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
Theme,
|
||||
Grid,
|
||||
Box,
|
||||
Paper,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Button,
|
||||
IconButton,
|
||||
} from '@material-ui/core';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import { makeStyles, Theme, Grid, Paper } from '@material-ui/core';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
|
||||
|
||||
import {
|
||||
CatalogIcon,
|
||||
@@ -56,8 +42,6 @@ import {
|
||||
createPageExtension,
|
||||
createExtensionInput,
|
||||
createNavItemExtension,
|
||||
createExtensionDataRef,
|
||||
createExtension,
|
||||
createSchemaFromZod,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
@@ -83,8 +67,6 @@ import { searchResultItemExtensionData } from '@backstage/plugin-search-react/al
|
||||
import { SearchClient } from './apis';
|
||||
import { SearchType } from './components/SearchType';
|
||||
import { UrlUpdater } from './components/SearchPage/SearchPage';
|
||||
import { SidebarSearchModal } from './components/SidebarSearchModal/SidebarSearchModal';
|
||||
import { useSearchModal } from './components/SearchModal';
|
||||
|
||||
/** @alpha */
|
||||
export const SearchApi = createApiExtension({
|
||||
@@ -248,249 +230,16 @@ export const SearchPage = createPageExtension({
|
||||
},
|
||||
});
|
||||
|
||||
const searchModalExtensionData = createExtensionDataRef<JSX.Element>(
|
||||
'plugin.search.modal.element',
|
||||
);
|
||||
|
||||
const useSearchModalStyles = makeStyles(theme => ({
|
||||
dialogTitle: {
|
||||
gap: theme.spacing(1),
|
||||
display: 'grid',
|
||||
alignItems: 'center',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
'&> button': {
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
container: {
|
||||
borderRadius: 30,
|
||||
display: 'flex',
|
||||
height: '2.4em',
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
filter: {
|
||||
'& + &': {
|
||||
marginTop: theme.spacing(2.5),
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
padding: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
input: {
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
'&:hover': {
|
||||
background: 'none',
|
||||
},
|
||||
},
|
||||
dialogActionsContainer: { padding: theme.spacing(1, 3) },
|
||||
viewResultsLink: { verticalAlign: '0.5em' },
|
||||
}));
|
||||
|
||||
/** @alpha */
|
||||
export const SearchModal = createExtension({
|
||||
id: 'plugin.search.modal',
|
||||
at: 'plugin.search.nav.index/modal',
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({
|
||||
noTrack: z.boolean().default(false),
|
||||
}),
|
||||
),
|
||||
inputs: {
|
||||
items: createExtensionInput({
|
||||
item: searchResultItemExtensionData,
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
element: searchModalExtensionData,
|
||||
},
|
||||
factory({ bind, config, inputs }) {
|
||||
const getResultItemComponent = (result: SearchResult) => {
|
||||
const value = inputs.items.find(({ item }) => item?.predicate?.(result));
|
||||
return value?.item.component ?? DefaultResultListItem;
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const classes = useSearchModalStyles();
|
||||
const navigate = useNavigate();
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const { types } = useSearch();
|
||||
const { toggleModal } = useSearchModal();
|
||||
const searchRootRoute = '/search';
|
||||
const searchBarRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
searchBarRef?.current?.focus();
|
||||
});
|
||||
|
||||
// This handler is called when "enter" is pressed
|
||||
const handleSearchBarSubmit = useCallback(() => {
|
||||
toggleModal();
|
||||
// Using ref to get the current field value without waiting for a query debounce
|
||||
const query = searchBarRef.current?.value ?? '';
|
||||
navigate(`${searchRootRoute}?query=${query}`);
|
||||
}, [navigate, toggleModal, searchRootRoute]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<Box className={classes.dialogTitle}>
|
||||
<SearchBar
|
||||
className={classes.input}
|
||||
inputProps={{ ref: searchBarRef }}
|
||||
onSubmit={handleSearchBarSubmit}
|
||||
/>
|
||||
|
||||
<IconButton aria-label="close" onClick={toggleModal}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Grid container direction="column">
|
||||
<Grid item>
|
||||
<SearchType.Tabs
|
||||
defaultValue="software-catalog"
|
||||
types={[
|
||||
{
|
||||
value: 'software-catalog',
|
||||
name: 'Software Catalog',
|
||||
},
|
||||
{
|
||||
value: 'techdocs',
|
||||
name: 'Documentation',
|
||||
},
|
||||
{
|
||||
value: 'tools',
|
||||
name: 'Tools',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item container>
|
||||
{types.includes('techdocs') && (
|
||||
<Grid item xs={2}>
|
||||
<SearchFilter.Select
|
||||
className={classes.filter}
|
||||
label="Entity"
|
||||
name="name"
|
||||
values={async () => {
|
||||
// Return a list of entities which are documented.
|
||||
const { items } = await catalogApi.getEntities({
|
||||
fields: ['metadata.name'],
|
||||
filter: {
|
||||
'metadata.annotations.backstage.io/techdocs-ref':
|
||||
CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
});
|
||||
|
||||
const names = items.map(entity => entity.metadata.name);
|
||||
names.sort();
|
||||
return names;
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={2}>
|
||||
<SearchFilter.Select
|
||||
className={classes.filter}
|
||||
label="Kind"
|
||||
name="kind"
|
||||
values={['Component', 'Template']}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<SearchFilter.Select
|
||||
className={classes.filter}
|
||||
label="Lifecycle"
|
||||
name="lifecycle"
|
||||
values={['experimental', 'production']}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={types.includes('techdocs') ? 6 : 8}
|
||||
container
|
||||
direction="row-reverse"
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Button
|
||||
className={classes.button}
|
||||
color="primary"
|
||||
endIcon={<ArrowForwardIcon />}
|
||||
onClick={handleSearchBarSubmit}
|
||||
disableRipple
|
||||
>
|
||||
View Full Results
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<SearchResults>
|
||||
{({ results }) =>
|
||||
results.map((result, index) => {
|
||||
const SearchResultListItem =
|
||||
getResultItemComponent(result);
|
||||
return (
|
||||
<SearchResultListItem
|
||||
key={index}
|
||||
rank={result.rank}
|
||||
result={result.document}
|
||||
noTrack={config.noTrack}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</SearchResults>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions className={classes.dialogActionsContainer}>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<SearchResultPager />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
bind({
|
||||
element: <Component />,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const SearchNavItem = createNavItemExtension({
|
||||
id: 'plugin.search.nav.index',
|
||||
routeRef: searchRouteRef,
|
||||
title: 'Search',
|
||||
icon: SearchIcon,
|
||||
group: async ({ inputs }) => (
|
||||
<SidebarSearchModal>{() => inputs.modal.element}</SidebarSearchModal>
|
||||
),
|
||||
inputs: {
|
||||
modal: createExtensionInput(
|
||||
{
|
||||
element: searchModalExtensionData,
|
||||
},
|
||||
{
|
||||
singleton: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export default createPlugin({
|
||||
id: 'plugin.search',
|
||||
extensions: [SearchApi, SearchPage, SearchModal, SearchNavItem],
|
||||
extensions: [SearchApi, SearchPage, SearchNavItem],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user