[DRAFT] Drawer Filters

Signed-off-by: Philipp Hugenroth <philipph@spotify.com>
This commit is contained in:
Philipp Hugenroth
2021-07-13 20:33:33 +02:00
parent 45b5fc3a83
commit 2934eb3a50
14 changed files with 269 additions and 164 deletions
@@ -59,10 +59,10 @@ const useStyles = (props: ContentHeaderProps) =>
type DefaultTitleProps = {
title?: string;
className: string;
className?: string;
};
const DefaultTitle = ({
export const ContentHeaderTitle = ({
title = 'Unknown page',
className,
}: DefaultTitleProps) => (
@@ -95,7 +95,7 @@ export const ContentHeader = ({
const renderedTitle = TitleComponent ? (
<TitleComponent />
) : (
<DefaultTitle title={title} className={classes.title} />
<ContentHeaderTitle title={title} className={classes.title} />
);
return (
<Fragment>
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { ContentHeader } from './ContentHeader';
export { ContentHeader, ContentHeaderTitle } from './ContentHeader';
@@ -23,7 +23,7 @@ export interface IProps extends HeaderProps {
themeId: string;
}
export const Layout = ({
export const PageWithHeader = ({
themeId,
children,
...props
@@ -15,3 +15,4 @@
*/
export { Page } from './Page';
export { PageWithHeader } from './PageWithHeader';
@@ -1,77 +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 { Grid } from '@material-ui/core';
import { Content, ContentHeader, SupportButton, Button } from '../..';
import { Layout, IProps as LayoutProps } from './Layout';
import { Link as RouterLink } from 'react-router-dom';
interface ILinkProps {
contentLink?: string;
contentLinkText?: string;
}
type IProps = LayoutProps &
ILinkProps & {
contentTitle: string;
supportMessage: string;
filter: React.ReactNode;
};
const TablePageLink = ({ contentLink, contentLinkText }: ILinkProps) => {
return contentLink && contentLinkText ? (
<Button
component={RouterLink}
variant="contained"
color="primary"
to={contentLink}
>
{contentLinkText}
</Button>
) : null;
};
export const TablePage = ({
supportMessage,
filter,
children,
contentTitle,
contentLink,
contentLinkText,
...props
}: React.PropsWithChildren<IProps>) => (
<Layout {...props}>
<Content>
<ContentHeader title={contentTitle}>
<TablePageLink
contentLink={contentLink}
contentLinkText={contentLinkText}
/>
<SupportButton>{supportMessage}</SupportButton>
</ContentHeader>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} lg={2}>
{filter}
</Grid>
<Grid item xs={12} sm={12} lg={10}>
{children}
</Grid>
</Grid>
</Content>
</Layout>
);
@@ -29,4 +29,3 @@ export * from './Sidebar';
export * from './SignInPage';
export * from './TabbedCard';
export * from './Breadcrumbs';
export * from './TablePage';
@@ -14,29 +14,10 @@
* limitations under the License.
*/
import { TableColumn, TablePage } from '@backstage/core-components';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import {
CatalogFilter,
CatalogTable,
CatalogTableRow,
} from '@backstage/plugin-catalog';
import {
EntityListProvider,
UserListFilterKind,
} from '@backstage/plugin-catalog-react';
import { TableColumn } from '@backstage/core-components';
import { CatalogTableRow } from '@backstage/plugin-catalog';
import { UserListFilterKind } from '@backstage/plugin-catalog-react';
import React from 'react';
import { createComponentRouteRef } from '../../routes';
const defaultColumns: TableColumn<CatalogTableRow>[] = [
CatalogTable.columns.createNameColumn({ defaultKind: 'API' }),
CatalogTable.columns.createSystemColumn(),
CatalogTable.columns.createOwnerColumn(),
CatalogTable.columns.createSpecTypeColumn(),
CatalogTable.columns.createSpecLifecycleColumn(),
CatalogTable.columns.createMetadataDescriptionColumn(),
CatalogTable.columns.createTagsColumn(),
];
interface IApiExplorerePageFilterProps {
initiallySelectedFilter?: UserListFilterKind;
@@ -46,36 +27,6 @@ export type ApiExplorerPageProps = IApiExplorerePageFilterProps & {
columns?: TableColumn<CatalogTableRow>[];
};
export const ApiExplorerPage = ({
initiallySelectedFilter = 'all',
columns,
}: ApiExplorerPageProps) => {
const createComponentLink = useRouteRef(createComponentRouteRef);
const configApi = useApi(configApiRef);
const generatedSubtitle = `${
configApi.getOptionalString('organization.name') ?? 'Backstage'
} API Explorer`;
return (
<EntityListProvider>
<TablePage
title="APIs"
subtitle={generatedSubtitle}
pageTitleOverride="APIs"
themeId="apis"
contentTitle=""
contentLink={createComponentLink ? createComponentLink() : ''}
contentLinkText="Register Existing API"
supportMessage="All your APIs"
filter={
<CatalogFilter
initialFilter="api"
initiallySelectedFilter={initiallySelectedFilter}
/>
}
>
<CatalogTable columns={columns || defaultColumns} />
</TablePage>
</EntityListProvider>
);
};
export const ApiExplorerPage = ({}: ApiExplorerPageProps) => (
<div>Please revert me...</div>
);
@@ -14,18 +14,35 @@
* limitations under the License.
*/
import React from 'react';
import {
Content,
ContentHeader,
ContentHeaderTitle,
PageWithHeader,
SupportButton,
TableColumn,
TableProps,
} from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import {
EntityKindPicker,
EntityLifecyclePicker,
EntityListProvider,
EntityOwnerPicker,
EntityTagPicker,
EntityTypePicker,
UserListFilterKind,
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { BackstageTheme } from '@backstage/theme';
import { Box, Grid, IconButton, useMediaQuery } from '@material-ui/core';
import FilterListIcon from '@material-ui/icons/FilterList';
import React, { useState } from 'react';
import { CatalogTable } from '../CatalogTable';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { EntityRow } from '../CatalogTable/types';
import { TableColumn, TableProps, TablePage } from '@backstage/core-components';
import { CatalogFilter } from '../CatalogFilter';
import { createComponentRouteRef } from '../../routes';
import { CreateComponentButton } from '../CreateComponentButton/CreateComponentButton';
import { FilterContainer } from '../FilterContainer';
import { FilteredTableLayout } from '../FilteredTableLayout';
export type CatalogPageProps = {
initiallySelectedFilter?: UserListFilterKind;
@@ -33,32 +50,87 @@ export type CatalogPageProps = {
actions?: TableProps<EntityRow>['actions'];
};
const CatalogPageHeaderAction = ({
showFilter,
toggleFilter,
isMidSizeScreen,
}: {
showFilter: boolean;
toggleFilter: (showFilter: boolean) => void;
isMidSizeScreen: boolean;
}) =>
isMidSizeScreen ? (
<Box display="flex" alignItems="center">
<ContentHeaderTitle title="Components" />
<IconButton onClick={() => toggleFilter(!showFilter)}>
<FilterListIcon />
</IconButton>
</Box>
) : (
<ContentHeaderTitle title="Components" />
);
const CatalogPageHeader = ({
children,
...props
}: React.PropsWithChildren<{
showFilter: boolean;
toggleFilter: (showFilter: boolean) => void;
isMidSizeScreen: boolean;
}>) => {
return (
<ContentHeader
titleComponent={() => <CatalogPageHeaderAction {...props} />}
>
{children}
</ContentHeader>
);
};
export const CatalogPage = ({
columns,
actions,
initiallySelectedFilter = 'owned',
}: CatalogPageProps) => {
const [showFilter, toggleFilter] = useState<boolean>(false);
const isMidSizeScreen = useMediaQuery<BackstageTheme>(theme =>
theme.breakpoints.down('md'),
);
const orgName =
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
const createComponentLink = useRouteRef(createComponentRouteRef);
return (
<EntityListProvider>
<TablePage
title={`${orgName} Catalog`}
subtitle={`Catalog of software components at ${orgName}`}
pageTitleOverride="Home"
themeId="home"
contentTitle="Components"
contentLink={createComponentLink ? createComponentLink() : ''}
contentLinkText="Create Component"
supportMessage="All your software catalog entities"
filter={
<CatalogFilter initiallySelectedFilter={initiallySelectedFilter} />
}
>
<CatalogTable columns={columns} actions={actions} />
</TablePage>
</EntityListProvider>
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
<Content>
<CatalogPageHeader
showFilter={showFilter}
toggleFilter={toggleFilter}
isMidSizeScreen={isMidSizeScreen}
>
<CreateComponentButton />
<SupportButton>All your software catalog entities</SupportButton>
</CatalogPageHeader>
<EntityListProvider>
<FilteredTableLayout>
<FilterContainer
showFilter={showFilter}
toggleFilter={toggleFilter}
isMidSizeScreen={isMidSizeScreen}
>
<EntityKindPicker initialFilter="component" hidden />
<EntityTypePicker />
<UserListPicker initialFilter={initiallySelectedFilter} />
<EntityOwnerPicker />
<EntityLifecyclePicker />
<EntityTagPicker />
</FilterContainer>
<Grid id="drawer-container" item xs={12} lg={10}>
<CatalogTable columns={columns} actions={actions} />
</Grid>
</FilteredTableLayout>
</EntityListProvider>
</Content>
</PageWithHeader>
);
};
@@ -0,0 +1,38 @@
/*
* 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 React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Button } from '@material-ui/core';
import { createComponentRouteRef } from '../../routes';
import { useRouteRef } from '@backstage/core-plugin-api';
export const CreateComponentButton = () => {
const createComponentLink = useRouteRef(createComponentRouteRef);
if (!createComponentLink) return null;
return (
<Button
component={RouterLink}
variant="contained"
color="primary"
to={createComponentLink()}
>
Create Component
</Button>
);
};
@@ -0,0 +1,17 @@
/*
* 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 { CreateComponentButton } from './CreateComponentButton';
@@ -0,0 +1,57 @@
/*
* 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 { Box, Drawer, Grid } from '@material-ui/core';
import React from 'react';
interface IProps {
showFilter: boolean;
toggleFilter: (showFilter: boolean) => void;
isMidSizeScreen: boolean;
}
export const FilterContainer = ({
children,
showFilter,
toggleFilter,
isMidSizeScreen,
}: React.PropsWithChildren<IProps>) =>
isMidSizeScreen ? (
<Drawer
open={showFilter}
onClose={() => {
toggleFilter(false);
}}
elevation={0}
disableAutoFocus
PaperProps={{
style: { position: 'absolute', width: '250px' },
}}
BackdropProps={{ style: { position: 'absolute' } }}
ModalProps={{
container: document.getElementById('drawer-container'),
disableEnforceFocus: true,
style: { position: 'absolute' },
}}
variant="temporary"
>
<Box m={1}>{children}</Box>
</Drawer>
) : (
<Grid item lg={2}>
{children}
</Grid>
);
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The Backstage Authors
* 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.
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { TablePage } from './TablePage';
export { FilterContainer } from './FilterContainer';
@@ -0,0 +1,30 @@
/*
* 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 React from 'react';
import { Grid } from '@material-ui/core';
interface IProps {}
export const FilteredTableLayout = ({
children,
}: React.PropsWithChildren<IProps>) => {
return (
<Grid container style={{ position: 'relative' }}>
{children}
</Grid>
);
};
@@ -0,0 +1,17 @@
/*
* 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 { FilteredTableLayout } from './FilteredTableLayout';