diff --git a/.changeset/wise-books-turn.md b/.changeset/wise-books-turn.md new file mode 100644 index 0000000000..77a42c5304 --- /dev/null +++ b/.changeset/wise-books-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Add Breadcrumbs component diff --git a/packages/core/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx new file mode 100644 index 0000000000..6f0ef0cb69 --- /dev/null +++ b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, List, ListItem, Popover, Typography } from '@material-ui/core'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import React, { Fragment } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { Breadcrumbs } from '.'; +import { Header, Page } from '..'; +import { Link } from '../../components/Link'; + +export default { + title: 'Layout/Breadcrumbs', + component: Breadcrumbs, +}; + +export const InHeader = () => ( + +

Standard breadcrumbs

+

+ Underlined pages are links. This should show a hierarchical relationship. +

+ + +
+ + +); + +export const OutsideOfHeader = () => { + const [anchorEl, setAnchorEl] = React.useState( + null, + ); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + return ( + +

+ It might be the case that you want to keep your breadcrumbs outside of + the header. In that case, they should be positioned above the title of + the page. +

+ +

Standard breadcrumbs

+

+ Underlined pages are links. This should show a hierarchical + relationship. +

+ + + + + General Page + Second Page + Current page + + +

Hidden breadcrumbs

+

+ Use this when you have more than three breadcrumbs. When user clicks on + ellipses, expand the breadcrumbs out. +

+ + + General Page + Second Page + Third Page + Fourth Page + Current page + + +

Layered breadcrumbs

+

+ Use this when you want to show alternative breadcrumbs on the same + hierarchical level. +

+ + + + General Page + + + Second Page + {open ? : } + + + Current page + + + + + Parallel second page + + + Another parallel second page + + + Yet another, parallel second page + + + + +
+ ); +}; diff --git a/packages/core/src/layout/Breadcrumbs/Breadcrumbs.test.tsx b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.test.tsx new file mode 100644 index 0000000000..0a490afb76 --- /dev/null +++ b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { Typography } from '@material-ui/core'; +import { fireEvent } from '@testing-library/react'; +import React from 'react'; +import { Link } from '../..'; +import { Breadcrumbs } from './Breadcrumbs'; + +describe('', () => { + it('should render', async () => { + const rendered = await renderInTestApp( + + General Page + Current Page + , + ); + expect(rendered.getByLabelText('breadcrumb')).toBeVisible(); + expect(rendered.getByText('General Page')).toBeVisible(); + expect(rendered.getByText('Current Page')).toBeVisible(); + }); + + it('should render hidden breadcrumbs', async () => { + const rendered = await renderInTestApp( + + General Page + Second Page + Third Page + Fourth Page + Current page + , + ); + expect(rendered.getByText('...')).toBeVisible(); + expect(rendered.queryByText('Third Page')).not.toBeInTheDocument(); + expect(rendered.queryByText('Fourth Page')).not.toBeInTheDocument(); + fireEvent.click(rendered.getByText('...')); + expect(rendered.getByText('Third Page')).toBeVisible(); + expect(rendered.getByText('Fourth Page')).toBeVisible(); + }); +}); diff --git a/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx new file mode 100644 index 0000000000..d8cf88497f --- /dev/null +++ b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, + Breadcrumbs as MaterialBreadcrumbs, + List, + ListItem, + Popover, + Typography, + withStyles, +} from '@material-ui/core'; +import React, { ComponentProps, Fragment } from 'react'; + +type Props = ComponentProps; + +const ClickableText = withStyles({ + root: { + textDecoration: 'underline', + cursor: 'pointer', + }, +})(Typography); + +const StyledBox = withStyles({ + root: { + textDecoration: 'underline', + color: 'inherit', + }, +})(Box); + +export const Breadcrumbs = ({ children, ...props }: Props) => { + const [anchorEl, setAnchorEl] = React.useState( + null, + ); + + const childrenArray = React.Children.toArray(children); + + const [firstPage, secondPage, ...expandablePages] = childrenArray; + const currentPage = expandablePages.length + ? expandablePages.pop() + : childrenArray[childrenArray.length - 1]; + const hasHiddenBreadcrumbs = childrenArray.length > 3; + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + return ( + + + {childrenArray.length > 1 && {firstPage}} + {childrenArray.length > 2 && {secondPage}} + {hasHiddenBreadcrumbs && ( + ... + )} + {currentPage} + + + + {expandablePages.map(pageLink => ( + + {pageLink} + + ))} + + + + ); +}; diff --git a/packages/core/src/layout/Breadcrumbs/index.ts b/packages/core/src/layout/Breadcrumbs/index.ts new file mode 100644 index 0000000000..6c5c2539df --- /dev/null +++ b/packages/core/src/layout/Breadcrumbs/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Breadcrumbs } from './Breadcrumbs'; diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 98db36ab14..73fa3066ea 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -16,15 +16,10 @@ import React, { ReactNode, CSSProperties, PropsWithChildren } from 'react'; import { Helmet } from 'react-helmet'; -import { - Link, - Typography, - Tooltip, - makeStyles, - Breadcrumbs, -} from '@material-ui/core'; -import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import { Typography, Tooltip, makeStyles } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; +import { Breadcrumbs } from '..'; +import { Link } from '../../components'; const useStyles = makeStyles(theme => ({ header: { @@ -136,15 +131,9 @@ const TypeFragment = ({ } return ( - } - className={classes.breadcrumb} - > - - {type} - - {pageTitle} + + {type} + {pageTitle} ); }; diff --git a/packages/core/src/layout/index.ts b/packages/core/src/layout/index.ts index d97d30982e..2ab1ebaf5b 100644 --- a/packages/core/src/layout/index.ts +++ b/packages/core/src/layout/index.ts @@ -28,3 +28,4 @@ export * from './Page'; export * from './Sidebar'; export * from './SignInPage'; export * from './TabbedCard'; +export * from './Breadcrumbs';