Merge pull request #6397 from tudi2d/catalog-filter-accordion
Wrap catalog filter with an accordion for smaller screens
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/test-utils': patch
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation.
|
||||
@@ -27,27 +27,33 @@ default catalog page and create a component in a
|
||||
```tsx
|
||||
// imports, etc omitted for brevity. for full source see:
|
||||
// https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
|
||||
export const CustomCatalogPage = () => {
|
||||
export const CustomCatalogPage = ({
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter = 'owned',
|
||||
}: CatalogPageProps) => {
|
||||
return (
|
||||
<CatalogLayout>
|
||||
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
|
||||
<Content>
|
||||
<ContentHeader title="Components">
|
||||
<CreateComponentButton />
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<div className={styles.contentWrapper}>
|
||||
<EntityListProvider>
|
||||
<div>
|
||||
<EntityListProvider>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<CatalogTable />
|
||||
</EntityListProvider>
|
||||
</div>
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</CatalogLayout>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -137,19 +143,27 @@ export const EntitySecurityTierPicker = () => {
|
||||
Now we can add the component to `CustomCatalogPage`:
|
||||
|
||||
```diff
|
||||
export const CustomCatalogPage = () => {
|
||||
export const CustomCatalogPage = ({
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter = 'owned',
|
||||
}: CatalogPageProps) => {
|
||||
return (
|
||||
...
|
||||
<EntityListProvider>
|
||||
<div>
|
||||
<EntityListProvider>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker />
|
||||
+ <EntitySecurityTierPicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
+ <EntitySecurityTierPicker />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<CatalogTable />
|
||||
</EntityListProvider>
|
||||
<FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1202,6 +1202,16 @@ export const Page: ({
|
||||
children,
|
||||
}: PropsWithChildren<Props_21>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "PageWithHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const PageWithHeader: ({
|
||||
themeId,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<PageWithHeaderProps>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "Progress" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
makeStyles,
|
||||
Popover,
|
||||
} from '@material-ui/core';
|
||||
import React, { Fragment, MouseEventHandler, useState } from 'react';
|
||||
import React, { MouseEventHandler, useState } from 'react';
|
||||
import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks';
|
||||
import { Link } from '../Link';
|
||||
|
||||
@@ -94,17 +94,17 @@ export const SupportButton = ({ title, children }: SupportButtonProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
data-testid="support-button"
|
||||
color="primary"
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
<Box marginRight={1}>
|
||||
<HelpIcon />
|
||||
</Box>
|
||||
Support
|
||||
</Button>
|
||||
<>
|
||||
<Box ml={1}>
|
||||
<Button
|
||||
data-testid="support-button"
|
||||
color="primary"
|
||||
onClick={onClickHandler}
|
||||
startIcon={<HelpIcon />}
|
||||
>
|
||||
Support
|
||||
</Button>
|
||||
</Box>
|
||||
<Popover
|
||||
data-testid="support-button-popover"
|
||||
open={popoverOpen}
|
||||
@@ -141,6 +141,6 @@ export const SupportButton = ({ title, children }: SupportButtonProps) => {
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Popover>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* TODO favoriteable capability
|
||||
*/
|
||||
|
||||
import React, { ComponentType, Fragment, PropsWithChildren } from 'react';
|
||||
import React, { ComponentType, PropsWithChildren } from 'react';
|
||||
import { Typography, makeStyles } from '@material-ui/core';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
@@ -57,15 +57,15 @@ const useStyles = (props: ContentHeaderProps) =>
|
||||
},
|
||||
}));
|
||||
|
||||
type DefaultTitleProps = {
|
||||
type ContentHeaderTitleProps = {
|
||||
title?: string;
|
||||
className: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const DefaultTitle = ({
|
||||
const ContentHeaderTitle = ({
|
||||
title = 'Unknown page',
|
||||
className,
|
||||
}: DefaultTitleProps) => (
|
||||
}: ContentHeaderTitleProps) => (
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h2"
|
||||
@@ -77,7 +77,7 @@ const DefaultTitle = ({
|
||||
);
|
||||
|
||||
type ContentHeaderProps = {
|
||||
title?: DefaultTitleProps['title'];
|
||||
title?: ContentHeaderTitleProps['title'];
|
||||
titleComponent?: ComponentType;
|
||||
description?: string;
|
||||
textAlign?: 'left' | 'right' | 'center';
|
||||
@@ -95,10 +95,10 @@ export const ContentHeader = ({
|
||||
const renderedTitle = TitleComponent ? (
|
||||
<TitleComponent />
|
||||
) : (
|
||||
<DefaultTitle title={title} className={classes.title} />
|
||||
<ContentHeaderTitle title={title} className={classes.title} />
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Helmet title={title} />
|
||||
<div className={classes.container}>
|
||||
<div className={classes.leftItemsBox}>
|
||||
@@ -111,6 +111,6 @@ export const ContentHeader = ({
|
||||
</div>
|
||||
<div className={classes.rightItemsBox}>{children}</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-21
@@ -14,27 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { PropsWithChildren, ComponentProps } from 'react';
|
||||
|
||||
import { Header, Page } from '@backstage/core-components';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { Header } from '../Header';
|
||||
import { Page } from './Page';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const ApiExplorerLayout = ({ children }: Props) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
} API Explorer`;
|
||||
return (
|
||||
<Page themeId="apis">
|
||||
<Header
|
||||
title="APIs"
|
||||
subtitle={generatedSubtitle}
|
||||
pageTitleOverride="APIs"
|
||||
/>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
type PageWithHeaderProps = ComponentProps<typeof Header> & {
|
||||
themeId: string;
|
||||
};
|
||||
|
||||
export const PageWithHeader = ({
|
||||
themeId,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<PageWithHeaderProps>) => (
|
||||
<Page themeId={themeId}>
|
||||
<Header {...props} />
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { Page } from './Page';
|
||||
export { PageWithHeader } from './PageWithHeader';
|
||||
|
||||
@@ -15,16 +15,15 @@ import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { StorageValueChange } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Breakpoint" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (ae-missing-release-tag) "mockBreakpoint" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function mockBreakpoint(
|
||||
initialBreakpoint?: Breakpoint,
|
||||
): {
|
||||
set(breakpoint: Breakpoint): void;
|
||||
remove(): void;
|
||||
};
|
||||
// @public
|
||||
export function mockBreakpoint({
|
||||
matches,
|
||||
}: {
|
||||
matches?: boolean | undefined;
|
||||
}): void;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "MockErrorApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -14,80 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
const queryToBreakpoint = {
|
||||
'(min-width:1920px)': 'xl',
|
||||
'(min-width:1280px)': 'lg',
|
||||
'(min-width:960px)': 'md',
|
||||
'(min-width:600px)': 'sm',
|
||||
'(min-width:0px)': 'xs',
|
||||
} as Record<string, Breakpoint>;
|
||||
|
||||
function toBreakpoint(query: string) {
|
||||
const breakpoint = queryToBreakpoint[query];
|
||||
if (!breakpoint) {
|
||||
throw new Error(
|
||||
`received unknown media query in breakpoint mock: '${query}'`,
|
||||
);
|
||||
}
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
type Listener = (event: { matches: boolean }) => void;
|
||||
|
||||
interface QueryList {
|
||||
addListener(listener: Listener): void;
|
||||
removeListener(listener: Listener): void;
|
||||
matches: boolean;
|
||||
}
|
||||
|
||||
interface Query {
|
||||
query: string;
|
||||
queryList: QueryList;
|
||||
listeners: Set<Listener>;
|
||||
}
|
||||
|
||||
export default function mockBreakpoint(initialBreakpoint: Breakpoint = 'xl') {
|
||||
let currentBreakpoint = initialBreakpoint;
|
||||
const queries = Array<Query>();
|
||||
|
||||
const previousMatchMedia: any = (window as any).matchMedia;
|
||||
|
||||
(window as any).matchMedia = (query: string): QueryList => {
|
||||
const listeners = new Set<Listener>();
|
||||
|
||||
const queryList: QueryList = {
|
||||
addListener(listener) {
|
||||
listeners.add(listener);
|
||||
},
|
||||
removeListener(listener) {
|
||||
listeners.delete(listener);
|
||||
},
|
||||
matches: toBreakpoint(query) === currentBreakpoint,
|
||||
};
|
||||
|
||||
queries.push({ query, queryList, listeners });
|
||||
|
||||
return queryList;
|
||||
};
|
||||
|
||||
return {
|
||||
set(breakpoint: Breakpoint) {
|
||||
currentBreakpoint = breakpoint;
|
||||
|
||||
act(() => {
|
||||
queries.forEach(({ query, queryList, listeners }) => {
|
||||
const matches = toBreakpoint(query) === breakpoint;
|
||||
queryList.matches = matches;
|
||||
listeners.forEach(listener => listener({ matches }));
|
||||
});
|
||||
});
|
||||
},
|
||||
remove() {
|
||||
(window as any).matchMedia = previousMatchMedia;
|
||||
},
|
||||
};
|
||||
/**
|
||||
* This is a mocking method suggested in the Jest Doc's, as it is not implemented in JSDOM yet.
|
||||
* It can be used to mock values when the MUI `useMediaQuery` hook if it is used in a tested component.
|
||||
*
|
||||
* For issues checkout the documentation:
|
||||
* https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
*
|
||||
* If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced
|
||||
* https://material-ui.com/components/use-media-query/#testing
|
||||
*
|
||||
* @param matchMediaOptions
|
||||
*/
|
||||
export default function mockBreakpoint({ matches = false }) {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: matches,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,14 +52,16 @@ const apiDocsPlugin: BackstagePlugin<
|
||||
export { apiDocsPlugin };
|
||||
export { apiDocsPlugin as plugin };
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ApiExplorerPageProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ApiExplorerPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ApiExplorerPage: ({
|
||||
initiallySelectedFilter,
|
||||
columns,
|
||||
}: ApiExplorerPageProps) => JSX.Element;
|
||||
}: {
|
||||
initiallySelectedFilter?: UserListFilterKind | undefined;
|
||||
columns?: TableColumn<CatalogTableRow>[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiTypeTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -14,6 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
PageWithHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
CatalogTable,
|
||||
CatalogTableRow,
|
||||
FilteredEntityLayout,
|
||||
EntityListContainer,
|
||||
FilterContainer,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
@@ -24,29 +39,10 @@ import {
|
||||
UserListFilterKind,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import { Button, makeStyles } from '@material-ui/core';
|
||||
import { Button } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { ApiExplorerLayout } from './ApiExplorerLayout';
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
display: 'grid',
|
||||
gridTemplateAreas: "'filters' 'table'",
|
||||
gridTemplateColumns: '250px 1fr',
|
||||
gridColumnGap: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
const defaultColumns: TableColumn<CatalogTableRow>[] = [
|
||||
CatalogTable.columns.createNameColumn({ defaultKind: 'API' }),
|
||||
@@ -58,7 +54,7 @@ const defaultColumns: TableColumn<CatalogTableRow>[] = [
|
||||
CatalogTable.columns.createTagsColumn(),
|
||||
];
|
||||
|
||||
export type ApiExplorerPageProps = {
|
||||
type ApiExplorerPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
columns?: TableColumn<CatalogTableRow>[];
|
||||
};
|
||||
@@ -67,11 +63,19 @@ export const ApiExplorerPage = ({
|
||||
initiallySelectedFilter = 'all',
|
||||
columns,
|
||||
}: ApiExplorerPageProps) => {
|
||||
const styles = useStyles();
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
} API Explorer`;
|
||||
|
||||
return (
|
||||
<ApiExplorerLayout>
|
||||
<PageWithHeader
|
||||
themeId="apis"
|
||||
title="APIs"
|
||||
subtitle={generatedSubtitle}
|
||||
pageTitleOverride="APIs"
|
||||
>
|
||||
<Content>
|
||||
<ContentHeader title="">
|
||||
{createComponentLink && (
|
||||
@@ -86,20 +90,22 @@ export const ApiExplorerPage = ({
|
||||
)}
|
||||
<SupportButton>All your APIs</SupportButton>
|
||||
</ContentHeader>
|
||||
<div className={styles.contentWrapper}>
|
||||
<EntityListProvider>
|
||||
<div>
|
||||
<EntityListProvider>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="api" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<CatalogTable columns={columns || defaultColumns} />
|
||||
</EntityListProvider>
|
||||
</div>
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns || defaultColumns} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</ApiExplorerLayout>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,8 +52,8 @@ export const MockEntityListContextProvider = ({
|
||||
const defaultContext: EntityListContextProps = {
|
||||
entities: [],
|
||||
backendEntities: [],
|
||||
updateFilters: updateFilters,
|
||||
filters: filters,
|
||||
updateFilters,
|
||||
filters,
|
||||
loading: false,
|
||||
queryParameters: {},
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ export function AboutCard({ variant }: AboutCardProps): JSX.Element;
|
||||
// Warning: (ae-missing-release-tag) "AboutContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const AboutContent: ({ entity }: Props_2) => JSX.Element;
|
||||
export const AboutContent: ({ entity }: Props) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "AboutField" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -50,7 +50,7 @@ export const AboutField: ({
|
||||
value,
|
||||
gridSizes,
|
||||
children,
|
||||
}: Props_3) => JSX.Element;
|
||||
}: Props_2) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CatalogClientWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -109,17 +109,11 @@ export const CatalogEntityPage: () => JSX.Element;
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CatalogIndexPage: ({
|
||||
initiallySelectedFilter,
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter,
|
||||
}: CatalogPageProps) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "CatalogLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CatalogLayout: ({ children }: Props) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "catalogPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -305,6 +299,13 @@ export const EntityLinksCard: ({
|
||||
variant?: 'gridItem' | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityListContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityListContainer: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityOrphanWarning" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
@@ -343,6 +344,20 @@ export const EntitySwitch: {
|
||||
// @public (undocumented)
|
||||
export const EntitySystemDiagramCard: SystemDiagramCard;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "FilterContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const FilterContainer: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "FilteredEntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const FilteredEntityLayout: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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 React from 'react';
|
||||
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Header, Page } from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CatalogLayout = ({ children }: Props) => {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={`${orgName} Catalog`}
|
||||
subtitle={`Catalog of software components at ${orgName}`}
|
||||
pageTitleOverride="Home"
|
||||
/>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default CatalogLayout;
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
MockStorageApi,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
mockBreakpoint,
|
||||
} from '@backstage/test-utils';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
@@ -244,4 +245,13 @@ describe('CatalogPage', () => {
|
||||
screen.findByText(/Starred \(1\)/),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should wrap filter in drawer on smaller screens', async () => {
|
||||
mockBreakpoint({ matches: true });
|
||||
const { getByRole } = await renderWrapped(<CatalogPage />);
|
||||
const button = getByRole('button', { name: 'Filters' });
|
||||
expect(getByRole('presentation', { hidden: true })).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
expect(getByRole('presentation')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,8 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
PageWithHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
TableProps,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
@@ -26,18 +33,15 @@ import {
|
||||
UserListFilterKind,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { CatalogTable } from '../CatalogTable';
|
||||
|
||||
import { EntityRow } from '../CatalogTable/types';
|
||||
import CatalogLayout from './CatalogLayout';
|
||||
import { CreateComponentButton } from '../CreateComponentButton';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
TableProps,
|
||||
} from '@backstage/core-components';
|
||||
FilteredEntityLayout,
|
||||
EntityListContainer,
|
||||
FilterContainer,
|
||||
} from '../FilteredEntityLayout';
|
||||
|
||||
export type CatalogPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
@@ -46,39 +50,36 @@ export type CatalogPageProps = {
|
||||
};
|
||||
|
||||
export const CatalogPage = ({
|
||||
initiallySelectedFilter = 'owned',
|
||||
columns,
|
||||
actions,
|
||||
}: CatalogPageProps) => (
|
||||
<CatalogLayout>
|
||||
<Content>
|
||||
<ContentHeader title="Components">
|
||||
<CreateComponentButton />
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<Grid container spacing={2}>
|
||||
initiallySelectedFilter = 'owned',
|
||||
}: CatalogPageProps) => {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
return (
|
||||
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
|
||||
<Content>
|
||||
<ContentHeader title="Components">
|
||||
<CreateComponentButton />
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<EntityListProvider>
|
||||
<Grid item sm={12} lg={2} alignContent="flex-start">
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} lg={10}>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</Grid>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Grid>
|
||||
</Content>
|
||||
</CatalogLayout>
|
||||
);
|
||||
</Content>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,5 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CatalogLayout } from './CatalogLayout';
|
||||
export { CatalogPage } from './CatalogPage';
|
||||
|
||||
@@ -23,9 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
export const CreateComponentButton = () => {
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
|
||||
if (!createComponentLink) return null;
|
||||
|
||||
return (
|
||||
return createComponentLink ? (
|
||||
<Button
|
||||
component={RouterLink}
|
||||
variant="contained"
|
||||
@@ -34,5 +32,5 @@ export const CreateComponentButton = () => {
|
||||
>
|
||||
Create Component
|
||||
</Button>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -13,4 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { CreateComponentButton } from './CreateComponentButton';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Grid } from '@material-ui/core';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
export const EntityListContainer = ({ children }: PropsWithChildren<{}>) => (
|
||||
<Grid item xs={12} lg={10}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 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 { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Drawer,
|
||||
Grid,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import FilterListIcon from '@material-ui/icons/FilterList';
|
||||
import React, { useState, PropsWithChildren } from 'react';
|
||||
|
||||
export const FilterContainer = ({ children }: PropsWithChildren<{}>) => {
|
||||
const isMidSizeScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('md'),
|
||||
);
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const [filterDrawerOpen, setFilterDrawerOpen] = useState<boolean>(false);
|
||||
|
||||
return isMidSizeScreen ? (
|
||||
<>
|
||||
<Button
|
||||
style={{ marginTop: theme.spacing(1), marginLeft: theme.spacing(1) }}
|
||||
onClick={() => setFilterDrawerOpen(true)}
|
||||
startIcon={<FilterListIcon />}
|
||||
>
|
||||
Filters
|
||||
</Button>
|
||||
<Drawer
|
||||
open={filterDrawerOpen}
|
||||
onClose={() => setFilterDrawerOpen(false)}
|
||||
anchor="left"
|
||||
disableAutoFocus
|
||||
keepMounted
|
||||
variant="temporary"
|
||||
>
|
||||
<Box m={2}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
style={{ marginBottom: theme.spacing(1) }}
|
||||
>
|
||||
Filters
|
||||
</Typography>
|
||||
{children}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
) : (
|
||||
<Grid item lg={2}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Grid } from '@material-ui/core';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
export const FilteredEntityLayout = ({ children }: PropsWithChildren<{}>) => (
|
||||
<Grid container style={{ position: 'relative' }}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { FilteredEntityLayout } from './FilteredEntityLayout';
|
||||
export { FilterContainer } from './FilterContainer';
|
||||
export { EntityListContainer } from './EntityListContainer';
|
||||
@@ -14,17 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './components/AboutCard';
|
||||
export { CatalogClientWrapper } from './CatalogClientWrapper';
|
||||
export { CatalogLayout } from './components/CatalogPage';
|
||||
export { CatalogResultListItem } from './components/CatalogResultListItem';
|
||||
export * from './components/AboutCard';
|
||||
export * from './components/CatalogResultListItem';
|
||||
export { CatalogTable } from './components/CatalogTable';
|
||||
export type { EntityRow as CatalogTableRow } from './components/CatalogTable';
|
||||
export { CreateComponentButton } from './components/CreateComponentButton';
|
||||
export { EntityLayout } from './components/EntityLayout';
|
||||
export * from './components/CatalogTable/columns';
|
||||
export * from './components/CreateComponentButton';
|
||||
export * from './components/EntityLayout';
|
||||
export * from './components/EntityOrphanWarning';
|
||||
export { EntityPageLayout } from './components/EntityPageLayout';
|
||||
export * from './components/EntityPageLayout';
|
||||
export * from './components/EntitySwitch';
|
||||
export * from './components/FilteredEntityLayout';
|
||||
export { Router } from './components/Router';
|
||||
export {
|
||||
CatalogEntityPage,
|
||||
@@ -32,8 +33,8 @@ export {
|
||||
catalogPlugin,
|
||||
catalogPlugin as plugin,
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependencyOfComponentsCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasResourcesCard,
|
||||
@@ -42,4 +43,3 @@ export {
|
||||
EntityLinksCard,
|
||||
EntitySystemDiagramCard,
|
||||
} from './plugin';
|
||||
export * from './components/CatalogTable/columns';
|
||||
|
||||
Reference in New Issue
Block a user