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.
+
+ 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';