Merge pull request #6397 from tudi2d/catalog-filter-accordion

Wrap catalog filter with an accordion for smaller screens
This commit is contained in:
Tim Hansen
2021-07-20 07:43:25 -06:00
committed by GitHub
24 changed files with 392 additions and 287 deletions
+8
View File
@@ -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>
...
};
```
+10
View File
@@ -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>
</>
);
};
@@ -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';
+7 -8
View File
@@ -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(),
})),
});
}
+4 -2
View File
@@ -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: {},
};
+24 -9
View File
@@ -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';
+8 -8
View File
@@ -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';