@backstage/plugin-catalog 1.33.0-next.1 → 1.33.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js +16 -10
  3. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js.map +1 -1
  4. package/dist/alpha/components/EntityTabs/EntityTabs.esm.js +28 -14
  5. package/dist/alpha/components/EntityTabs/EntityTabs.esm.js.map +1 -1
  6. package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js +76 -52
  7. package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js.map +1 -1
  8. package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js +34 -44
  9. package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js.map +1 -1
  10. package/dist/alpha/pages.esm.js +36 -51
  11. package/dist/alpha/pages.esm.js.map +1 -1
  12. package/dist/alpha/translation.esm.js +17 -3
  13. package/dist/alpha/translation.esm.js.map +1 -1
  14. package/dist/alpha.d.ts +69 -47
  15. package/dist/components/AboutCard/AboutCard.esm.js +2 -2
  16. package/dist/components/AboutCard/AboutCard.esm.js.map +1 -1
  17. package/dist/components/AboutCard/AboutContent.esm.js +1 -1
  18. package/dist/components/AboutCard/AboutContent.esm.js.map +1 -1
  19. package/dist/components/AboutCard/AboutField.esm.js +4 -1
  20. package/dist/components/AboutCard/AboutField.esm.js.map +1 -1
  21. package/dist/components/CatalogSearchResultListItem/CatalogSearchResultListItem.esm.js +15 -3
  22. package/dist/components/CatalogSearchResultListItem/CatalogSearchResultListItem.esm.js.map +1 -1
  23. package/dist/components/CatalogTable/CatalogTable.esm.js +3 -1
  24. package/dist/components/CatalogTable/CatalogTable.esm.js.map +1 -1
  25. package/dist/components/EntityContextMenu/EntityContextMenu.esm.js +1 -1
  26. package/dist/components/EntityContextMenu/EntityContextMenu.esm.js.map +1 -1
  27. package/dist/components/EntityLayout/EntityLayout.esm.js +3 -7
  28. package/dist/components/EntityLayout/EntityLayout.esm.js.map +1 -1
  29. package/dist/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.esm.js +4 -4
  30. package/dist/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.esm.js.map +1 -1
  31. package/dist/components/EntityRelationWarning/EntityRelationWarning.esm.js +16 -9
  32. package/dist/components/EntityRelationWarning/EntityRelationWarning.esm.js.map +1 -1
  33. package/dist/package.json.esm.js +3 -3
  34. package/package.json +20 -20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @backstage/plugin-catalog
2
2
 
3
+ ## 1.33.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend)
8
+
9
+ ### Patch Changes
10
+
11
+ - 220d6c3: Add missing translation entries for catalog UI text.
12
+
13
+ This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation.
14
+
15
+ No functional behavior is changed aside from the improved internationalization support.
16
+
17
+ - 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions.
18
+ - a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
19
+ - 75ac651: Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI.
20
+ - Updated dependencies
21
+ - @backstage/ui@0.12.0-next.2
22
+ - @backstage/plugin-catalog-react@2.0.0-next.2
23
+ - @backstage/catalog-client@1.12.2-next.0
24
+ - @backstage/frontend-plugin-api@0.14.0-next.2
25
+ - @backstage/integration-react@1.2.15-next.2
26
+ - @backstage/core-compat-api@0.5.8-next.2
27
+ - @backstage/core-components@0.18.7-next.2
28
+ - @backstage/core-plugin-api@1.12.3-next.1
29
+ - @backstage/plugin-permission-react@0.4.40-next.1
30
+ - @backstage/version-bridge@1.0.12-next.0
31
+ - @backstage/plugin-techdocs-react@1.3.8-next.1
32
+ - @backstage/plugin-search-react@1.10.3-next.2
33
+
3
34
  ## 1.33.0-next.1
4
35
 
5
36
  ### Minor Changes
@@ -20,7 +20,9 @@ const EntityLayout = (props) => {
20
20
  children,
21
21
  header,
22
22
  NotFoundComponent,
23
- parentEntityRelations
23
+ parentEntityRelations,
24
+ groupDefinitions,
25
+ showNavItemIcons
24
26
  } = props;
25
27
  const { kind } = useRouteRefParams(entityRouteRef);
26
28
  const { entity, loading, error } = useAsyncEntity();
@@ -41,7 +43,8 @@ const EntityLayout = (props) => {
41
43
  path: elementProps.path,
42
44
  title: elementProps.title,
43
45
  group: elementProps.group,
44
- children: elementProps.children
46
+ children: elementProps.children,
47
+ icon: elementProps.icon
45
48
  }
46
49
  ];
47
50
  }),
@@ -59,16 +62,19 @@ const EntityLayout = (props) => {
59
62
  }
60
63
  ),
61
64
  loading && /* @__PURE__ */ jsx(Progress, {}),
62
- entity && /* @__PURE__ */ jsx(EntityTabs, { routes }),
65
+ entity && /* @__PURE__ */ jsx(
66
+ EntityTabs,
67
+ {
68
+ routes,
69
+ groupDefinitions,
70
+ showIcons: showNavItemIcons
71
+ }
72
+ ),
63
73
  error && /* @__PURE__ */ jsx(Content, { children: /* @__PURE__ */ jsx(Alert, { severity: "error", children: error.toString() }) }),
64
- !loading && !error && !entity && /* @__PURE__ */ jsx(Content, { children: NotFoundComponent ? NotFoundComponent : /* @__PURE__ */ jsxs(WarningPanel, { title: t("entityLabels.warningPanelTitle"), children: [
65
- "There is no ",
74
+ !loading && !error && !entity && /* @__PURE__ */ jsx(Content, { children: NotFoundComponent ? NotFoundComponent : /* @__PURE__ */ jsx(WarningPanel, { title: t("entityLabels.warningPanelTitle"), children: t("entityPage.notFoundMessage", {
66
75
  kind,
67
- " with the requested",
68
- " ",
69
- /* @__PURE__ */ jsx(Link, { to: "https://backstage.io/docs/features/software-catalog/references", children: "kind, namespace, and name" }),
70
- "."
71
- ] }) })
76
+ link: /* @__PURE__ */ jsx(Link, { to: "https://backstage.io/docs/features/software-catalog/references", children: t("entityPage.notFoundLinkText") })
77
+ }) }) })
72
78
  ] });
73
79
  };
74
80
  EntityLayout.Route = Route;
@@ -1 +1 @@
1
- {"version":3,"file":"EntityLayout.esm.js","sources":["../../../../src/alpha/components/EntityLayout/EntityLayout.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ComponentProps, ReactNode } from 'react';\n\nimport Alert from '@material-ui/lab/Alert';\n\nimport {\n attachComponentData,\n useElementFilter,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n Content,\n Link,\n Page,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport { Entity } from '@backstage/catalog-model';\nimport {\n entityRouteRef,\n useAsyncEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { catalogTranslationRef } from '../../translation';\nimport { EntityHeader } from '../EntityHeader';\nimport { EntityTabs } from '../EntityTabs';\n\nexport type EntityLayoutRouteProps = {\n path: string;\n title: string;\n group: string;\n children: JSX.Element;\n if?: (entity: Entity) => boolean;\n};\n\nconst dataKey = 'plugin.catalog.entityLayoutRoute';\nconst Route: (props: EntityLayoutRouteProps) => null = () => null;\nattachComponentData(Route, dataKey, true);\nattachComponentData(Route, 'core.gatherMountPoints', true); // This causes all mount points that are discovered within this route to use the path of the route itself\n\n/** @public */\nexport interface EntityLayoutProps {\n UNSTABLE_contextMenuOptions?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_contextMenuOptions'];\n UNSTABLE_extraContextMenuItems?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_extraContextMenuItems'];\n contextMenuItems?: ComponentProps<typeof EntityHeader>['contextMenuItems'];\n children?: ReactNode;\n header?: JSX.Element;\n NotFoundComponent?: ReactNode;\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n}\n\n/**\n * EntityLayout is a compound component, which allows you to define a layout for\n * entities using a sub-navigation mechanism.\n *\n * Consists of two parts: EntityLayout and EntityLayout.Route\n *\n * @example\n * ```jsx\n * <EntityLayout>\n * <EntityLayout.Route path=\"/example\" title=\"Example tab\">\n * <div>This is rendered under /example/anything-here route</div>\n * </EntityLayout.Route>\n * </EntityLayout>\n * ```\n *\n * @public\n */\nexport const EntityLayout = (props: EntityLayoutProps) => {\n const {\n UNSTABLE_extraContextMenuItems,\n UNSTABLE_contextMenuOptions,\n contextMenuItems,\n children,\n header,\n NotFoundComponent,\n parentEntityRelations,\n } = props;\n const { kind } = useRouteRefParams(entityRouteRef);\n const { entity, loading, error } = useAsyncEntity();\n\n const routes = useElementFilter(\n children,\n elements =>\n elements\n .selectByComponentData({\n key: dataKey,\n withStrictError:\n 'Child of EntityLayout must be an EntityLayout.Route',\n })\n .getElements<EntityLayoutRouteProps>() // all nodes, element data, maintain structure or not?\n .flatMap(({ props: elementProps }) => {\n if (!entity) {\n return [];\n }\n if (elementProps.if && !elementProps.if(entity)) {\n return [];\n }\n return [\n {\n path: elementProps.path,\n title: elementProps.title,\n group: elementProps.group,\n children: elementProps.children,\n },\n ];\n }),\n [entity],\n );\n\n const { t } = useTranslationRef(catalogTranslationRef);\n\n return (\n <Page themeId={entity?.spec?.type?.toString() ?? 'home'}>\n {header ?? (\n <EntityHeader\n parentEntityRelations={parentEntityRelations}\n UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n contextMenuItems={contextMenuItems}\n />\n )}\n\n {loading && <Progress />}\n\n {entity && <EntityTabs routes={routes} />}\n\n {error && (\n <Content>\n <Alert severity=\"error\">{error.toString()}</Alert>\n </Content>\n )}\n\n {!loading && !error && !entity && (\n <Content>\n {NotFoundComponent ? (\n NotFoundComponent\n ) : (\n <WarningPanel title={t('entityLabels.warningPanelTitle')}>\n There is no {kind} with the requested{' '}\n <Link to=\"https://backstage.io/docs/features/software-catalog/references\">\n kind, namespace, and name\n </Link>\n .\n </WarningPanel>\n )}\n </Content>\n )}\n </Page>\n );\n};\n\nEntityLayout.Route = Route;\n"],"names":[],"mappings":";;;;;;;;;;AAmDA,MAAM,OAAA,GAAU,kCAAA;AAChB,MAAM,QAAiD,MAAM,IAAA;AAC7D,mBAAA,CAAoB,KAAA,EAAO,SAAS,IAAI,CAAA;AACxC,mBAAA,CAAoB,KAAA,EAAO,0BAA0B,IAAI,CAAA;AA4ClD,MAAM,YAAA,GAAe,CAAC,KAAA,KAA6B;AACxD,EAAA,MAAM;AAAA,IACJ,8BAAA;AAAA,IACA,2BAAA;AAAA,IACA,gBAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,iBAAA;AAAA,IACA;AAAA,GACF,GAAI,KAAA;AACJ,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,iBAAA,CAAkB,cAAc,CAAA;AACjD,EAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,KAAA,KAAU,cAAA,EAAe;AAElD,EAAA,MAAM,MAAA,GAAS,gBAAA;AAAA,IACb,QAAA;AAAA,IACA,CAAA,QAAA,KACE,SACG,qBAAA,CAAsB;AAAA,MACrB,GAAA,EAAK,OAAA;AAAA,MACL,eAAA,EACE;AAAA,KACH,EACA,WAAA,EAAoC,CACpC,QAAQ,CAAC,EAAE,KAAA,EAAO,YAAA,EAAa,KAAM;AACpC,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAO,EAAC;AAAA,MACV;AACA,MAAA,IAAI,aAAa,EAAA,IAAM,CAAC,YAAA,CAAa,EAAA,CAAG,MAAM,CAAA,EAAG;AAC/C,QAAA,OAAO,EAAC;AAAA,MACV;AACA,MAAA,OAAO;AAAA,QACL;AAAA,UACE,MAAM,YAAA,CAAa,IAAA;AAAA,UACnB,OAAO,YAAA,CAAa,KAAA;AAAA,UACpB,OAAO,YAAA,CAAa,KAAA;AAAA,UACpB,UAAU,YAAA,CAAa;AAAA;AACzB,OACF;AAAA,IACF,CAAC,CAAA;AAAA,IACL,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,EAAE,CAAA,EAAE,GAAI,iBAAA,CAAkB,qBAAqB,CAAA;AAErD,EAAA,uBACE,IAAA,CAAC,QAAK,OAAA,EAAS,MAAA,EAAQ,MAAM,IAAA,EAAM,QAAA,MAAc,MAAA,EAC9C,QAAA,EAAA;AAAA,IAAA,MAAA,oBACC,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,qBAAA;AAAA,QACA,2BAAA;AAAA,QACA,8BAAA;AAAA,QACA;AAAA;AAAA,KACF;AAAA,IAGD,OAAA,wBAAY,QAAA,EAAA,EAAS,CAAA;AAAA,IAErB,MAAA,oBAAU,GAAA,CAAC,UAAA,EAAA,EAAW,MAAA,EAAgB,CAAA;AAAA,IAEtC,KAAA,oBACC,GAAA,CAAC,OAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,UAAS,OAAA,EAAS,QAAA,EAAA,KAAA,CAAM,QAAA,EAAS,EAAE,CAAA,EAC5C,CAAA;AAAA,IAGD,CAAC,OAAA,IAAW,CAAC,KAAA,IAAS,CAAC,MAAA,oBACtB,GAAA,CAAC,OAAA,EAAA,EACE,QAAA,EAAA,iBAAA,GACC,oCAEA,IAAA,CAAC,YAAA,EAAA,EAAa,KAAA,EAAO,CAAA,CAAE,gCAAgC,CAAA,EAAG,QAAA,EAAA;AAAA,MAAA,cAAA;AAAA,MAC3C,IAAA;AAAA,MAAK,qBAAA;AAAA,MAAoB,GAAA;AAAA,sBACtC,GAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAG,gEAAA,EAAiE,QAAA,EAAA,2BAAA,EAE1E,CAAA;AAAA,MAAO;AAAA,KAAA,EAET,CAAA,EAEJ;AAAA,GAAA,EAEJ,CAAA;AAEJ;AAEA,YAAA,CAAa,KAAA,GAAQ,KAAA;;;;"}
1
+ {"version":3,"file":"EntityLayout.esm.js","sources":["../../../../src/alpha/components/EntityLayout/EntityLayout.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ComponentProps, ReactNode, ReactElement } from 'react';\n\nimport Alert from '@material-ui/lab/Alert';\n\nimport {\n attachComponentData,\n useElementFilter,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n Content,\n Link,\n Page,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport { Entity } from '@backstage/catalog-model';\nimport {\n entityRouteRef,\n useAsyncEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { catalogTranslationRef } from '../../translation';\nimport { EntityHeader } from '../EntityHeader';\nimport { EntityTabs } from '../EntityTabs';\nimport { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha';\n\nexport type EntityLayoutRouteProps = {\n path: string;\n title: string;\n group?: string;\n icon?: string | ReactElement;\n children: JSX.Element;\n if?: (entity: Entity) => boolean;\n};\n\nconst dataKey = 'plugin.catalog.entityLayoutRoute';\nconst Route: (props: EntityLayoutRouteProps) => null = () => null;\nattachComponentData(Route, dataKey, true);\nattachComponentData(Route, 'core.gatherMountPoints', true); // This causes all mount points that are discovered within this route to use the path of the route itself\n\n/** @public */\nexport interface EntityLayoutProps {\n UNSTABLE_contextMenuOptions?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_contextMenuOptions'];\n UNSTABLE_extraContextMenuItems?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_extraContextMenuItems'];\n contextMenuItems?: ComponentProps<typeof EntityHeader>['contextMenuItems'];\n children?: ReactNode;\n header?: JSX.Element;\n NotFoundComponent?: ReactNode;\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n groupDefinitions: EntityContentGroupDefinitions;\n showNavItemIcons?: boolean;\n}\n\n/**\n * EntityLayout is a compound component, which allows you to define a layout for\n * entities using a sub-navigation mechanism.\n *\n * Consists of two parts: EntityLayout and EntityLayout.Route\n *\n * @example\n * ```jsx\n * <EntityLayout>\n * <EntityLayout.Route path=\"/example\" title=\"Example tab\">\n * <div>This is rendered under /example/anything-here route</div>\n * </EntityLayout.Route>\n * </EntityLayout>\n * ```\n *\n * @public\n */\nexport const EntityLayout = (props: EntityLayoutProps) => {\n const {\n UNSTABLE_extraContextMenuItems,\n UNSTABLE_contextMenuOptions,\n contextMenuItems,\n children,\n header,\n NotFoundComponent,\n parentEntityRelations,\n groupDefinitions,\n showNavItemIcons,\n } = props;\n const { kind } = useRouteRefParams(entityRouteRef);\n const { entity, loading, error } = useAsyncEntity();\n\n const routes = useElementFilter(\n children,\n elements =>\n elements\n .selectByComponentData({\n key: dataKey,\n withStrictError:\n 'Child of EntityLayout must be an EntityLayout.Route',\n })\n .getElements<EntityLayoutRouteProps>() // all nodes, element data, maintain structure or not?\n .flatMap(({ props: elementProps }) => {\n if (!entity) {\n return [];\n }\n if (elementProps.if && !elementProps.if(entity)) {\n return [];\n }\n return [\n {\n path: elementProps.path,\n title: elementProps.title,\n group: elementProps.group,\n children: elementProps.children,\n icon: elementProps.icon,\n },\n ];\n }),\n [entity],\n );\n\n const { t } = useTranslationRef(catalogTranslationRef);\n\n return (\n <Page themeId={entity?.spec?.type?.toString() ?? 'home'}>\n {header ?? (\n <EntityHeader\n parentEntityRelations={parentEntityRelations}\n UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n contextMenuItems={contextMenuItems}\n />\n )}\n\n {loading && <Progress />}\n\n {entity && (\n <EntityTabs\n routes={routes}\n groupDefinitions={groupDefinitions}\n showIcons={showNavItemIcons}\n />\n )}\n\n {error && (\n <Content>\n <Alert severity=\"error\">{error.toString()}</Alert>\n </Content>\n )}\n\n {!loading && !error && !entity && (\n <Content>\n {NotFoundComponent ? (\n NotFoundComponent\n ) : (\n <WarningPanel title={t('entityLabels.warningPanelTitle')}>\n {t('entityPage.notFoundMessage', {\n kind,\n link: (\n <Link to=\"https://backstage.io/docs/features/software-catalog/references\">\n {t('entityPage.notFoundLinkText')}\n </Link>\n ),\n })}\n </WarningPanel>\n )}\n </Content>\n )}\n </Page>\n );\n};\n\nEntityLayout.Route = Route;\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { useMemo } from 'react';
3
3
  import { Helmet } from 'react-helmet';
4
- import { useParams, useRoutes, matchRoutes } from 'react-router-dom';
4
+ import { useParams, Outlet, useRoutes, matchRoutes } from 'react-router-dom';
5
5
  import { EntityTabsPanel } from './EntityTabsPanel.esm.js';
6
6
  import { EntityTabsList } from './EntityTabsList.esm.js';
7
7
 
@@ -9,22 +9,27 @@ function useSelectedSubRoute(subRoutes) {
9
9
  const params = useParams();
10
10
  const routes = subRoutes.map(({ path, children }) => ({
11
11
  caseSensitive: false,
12
- path: `${path}/*`,
13
- element: children
12
+ path,
13
+ element: /* @__PURE__ */ jsx(Outlet, {}),
14
+ children: [
15
+ {
16
+ index: true,
17
+ element: children
18
+ },
19
+ {
20
+ path: "*",
21
+ element: children
22
+ }
23
+ ]
14
24
  }));
15
- const sortedRoutes = routes.sort(
16
- (a, b) => (
17
- // remove "/*" symbols from path end before comparing
18
- b.path.replace(/\/\*$/, "").localeCompare(a.path.replace(/\/\*$/, ""))
19
- )
20
- );
25
+ const sortedRoutes = routes.sort((a, b) => b.path.localeCompare(a.path));
21
26
  const element = useRoutes(sortedRoutes) ?? subRoutes[0]?.children;
22
27
  let currentRoute = params["*"] ?? "";
23
28
  if (!currentRoute.startsWith("/")) {
24
29
  currentRoute = `/${currentRoute}`;
25
30
  }
26
31
  const [matchedRoute] = matchRoutes(sortedRoutes, currentRoute) ?? [];
27
- const foundIndex = matchedRoute ? subRoutes.findIndex((t) => `${t.path}/*` === matchedRoute.route.path) : 0;
32
+ const foundIndex = matchedRoute ? subRoutes.findIndex((t) => t.path === matchedRoute.route.path) : 0;
28
33
  return {
29
34
  index: foundIndex === -1 ? 0 : foundIndex,
30
35
  element,
@@ -32,11 +37,11 @@ function useSelectedSubRoute(subRoutes) {
32
37
  };
33
38
  }
34
39
  function EntityTabs(props) {
35
- const { routes } = props;
40
+ const { routes, groupDefinitions, showIcons } = props;
36
41
  const { index, route, element } = useSelectedSubRoute(routes);
37
42
  const tabs = useMemo(
38
43
  () => routes.map((t) => {
39
- const { path, title, group } = t;
44
+ const { path, title, group, icon } = t;
40
45
  let to = path;
41
46
  to = to.replace(/\/\*$/, "");
42
47
  to = to.replace(/^\//, "");
@@ -44,13 +49,22 @@ function EntityTabs(props) {
44
49
  group,
45
50
  id: path,
46
51
  path: to,
47
- label: title
52
+ label: title,
53
+ icon
48
54
  };
49
55
  }),
50
56
  [routes]
51
57
  );
52
58
  return /* @__PURE__ */ jsxs(Fragment, { children: [
53
- /* @__PURE__ */ jsx(EntityTabsList, { tabs, selectedIndex: index }),
59
+ /* @__PURE__ */ jsx(
60
+ EntityTabsList,
61
+ {
62
+ tabs,
63
+ selectedIndex: index,
64
+ showIcons,
65
+ groupDefinitions
66
+ }
67
+ ),
54
68
  /* @__PURE__ */ jsxs(EntityTabsPanel, { children: [
55
69
  /* @__PURE__ */ jsx(Helmet, { title: route?.title }),
56
70
  element
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTabs.esm.js","sources":["../../../../src/alpha/components/EntityTabs/EntityTabs.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useMemo } from 'react';\nimport { Helmet } from 'react-helmet';\nimport { matchRoutes, useParams, useRoutes } from 'react-router-dom';\nimport { EntityTabsPanel } from './EntityTabsPanel';\nimport { EntityTabsList } from './EntityTabsList';\n\ntype SubRoute = {\n group: string;\n path: string;\n title: string;\n children: JSX.Element;\n};\n\nexport function useSelectedSubRoute(subRoutes: SubRoute[]): {\n index: number;\n route?: SubRoute;\n element?: JSX.Element;\n} {\n const params = useParams();\n\n const routes = subRoutes.map(({ path, children }) => ({\n caseSensitive: false,\n path: `${path}/*`,\n element: children,\n }));\n\n // TODO: remove once react-router updated\n const sortedRoutes = routes.sort((a, b) =>\n // remove \"/*\" symbols from path end before comparing\n b.path.replace(/\\/\\*$/, '').localeCompare(a.path.replace(/\\/\\*$/, '')),\n );\n\n const element = useRoutes(sortedRoutes) ?? subRoutes[0]?.children;\n\n // TODO(Rugvip): Once we only support v6 stable we can always prefix\n // This avoids having a double / prefix for react-router v6 beta, which in turn breaks\n // the tab highlighting when using relative paths for the tabs.\n let currentRoute = params['*'] ?? '';\n if (!currentRoute.startsWith('/')) {\n currentRoute = `/${currentRoute}`;\n }\n\n const [matchedRoute] = matchRoutes(sortedRoutes, currentRoute) ?? [];\n const foundIndex = matchedRoute\n ? subRoutes.findIndex(t => `${t.path}/*` === matchedRoute.route.path)\n : 0;\n\n return {\n index: foundIndex === -1 ? 0 : foundIndex,\n element,\n route: subRoutes[foundIndex] ?? subRoutes[0],\n };\n}\n\ntype EntityTabsProps = {\n routes: SubRoute[];\n};\n\nexport function EntityTabs(props: EntityTabsProps) {\n const { routes } = props;\n\n const { index, route, element } = useSelectedSubRoute(routes);\n\n const tabs = useMemo(\n () =>\n routes.map(t => {\n const { path, title, group } = t;\n let to = path;\n // Remove trailing /*\n to = to.replace(/\\/\\*$/, '');\n // And remove leading / for relative navigation\n to = to.replace(/^\\//, '');\n return {\n group,\n id: path,\n path: to,\n label: title,\n };\n }),\n [routes],\n );\n\n return (\n <>\n <EntityTabsList tabs={tabs} selectedIndex={index} />\n <EntityTabsPanel>\n <Helmet title={route?.title} />\n {element}\n </EntityTabsPanel>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;AA4BO,SAAS,oBAAoB,SAAA,EAIlC;AACA,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,MAAM,SAAS,SAAA,CAAU,GAAA,CAAI,CAAC,EAAE,IAAA,EAAM,UAAS,MAAO;AAAA,IACpD,aAAA,EAAe,KAAA;AAAA,IACf,IAAA,EAAM,GAAG,IAAI,CAAA,EAAA,CAAA;AAAA,IACb,OAAA,EAAS;AAAA,GACX,CAAE,CAAA;AAGF,EAAA,MAAM,eAAe,MAAA,CAAO,IAAA;AAAA,IAAK,CAAC,CAAA,EAAG,CAAA;AAAA;AAAA,MAEnC,CAAA,CAAE,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAA,CAAE,aAAA,CAAc,CAAA,CAAE,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAC;AAAA;AAAA,GACvE;AAEA,EAAA,MAAM,UAAU,SAAA,CAAU,YAAY,CAAA,IAAK,SAAA,CAAU,CAAC,CAAA,EAAG,QAAA;AAKzD,EAAA,IAAI,YAAA,GAAe,MAAA,CAAO,GAAG,CAAA,IAAK,EAAA;AAClC,EAAA,IAAI,CAAC,YAAA,CAAa,UAAA,CAAW,GAAG,CAAA,EAAG;AACjC,IAAA,YAAA,GAAe,IAAI,YAAY,CAAA,CAAA;AAAA,EACjC;AAEA,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,YAAY,YAAA,EAAc,YAAY,KAAK,EAAC;AACnE,EAAA,MAAM,UAAA,GAAa,YAAA,GACf,SAAA,CAAU,SAAA,CAAU,CAAA,CAAA,KAAK,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,EAAA,CAAA,KAAS,YAAA,CAAa,KAAA,CAAM,IAAI,CAAA,GAClE,CAAA;AAEJ,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA,KAAe,EAAA,GAAK,CAAA,GAAI,UAAA;AAAA,IAC/B,OAAA;AAAA,IACA,KAAA,EAAO,SAAA,CAAU,UAAU,CAAA,IAAK,UAAU,CAAC;AAAA,GAC7C;AACF;AAMO,SAAS,WAAW,KAAA,EAAwB;AACjD,EAAA,MAAM,EAAE,QAAO,GAAI,KAAA;AAEnB,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,OAAA,EAAQ,GAAI,oBAAoB,MAAM,CAAA;AAE5D,EAAA,MAAM,IAAA,GAAO,OAAA;AAAA,IACX,MACE,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK;AACd,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,KAAA,EAAM,GAAI,CAAA;AAC/B,MAAA,IAAI,EAAA,GAAK,IAAA;AAET,MAAA,EAAA,GAAK,EAAA,CAAG,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAA;AAE3B,MAAA,EAAA,GAAK,EAAA,CAAG,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACzB,MAAA,OAAO;AAAA,QACL,KAAA;AAAA,QACA,EAAA,EAAI,IAAA;AAAA,QACJ,IAAA,EAAM,EAAA;AAAA,QACN,KAAA,EAAO;AAAA,OACT;AAAA,IACF,CAAC,CAAA;AAAA,IACH,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAY,aAAA,EAAe,KAAA,EAAO,CAAA;AAAA,yBACjD,eAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,MAAA,EAAA,EAAO,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,CAAA;AAAA,MAC5B;AAAA,KAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"EntityTabs.esm.js","sources":["../../../../src/alpha/components/EntityTabs/EntityTabs.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ReactElement, useMemo } from 'react';\nimport { Helmet } from 'react-helmet';\nimport { matchRoutes, useParams, useRoutes, Outlet } from 'react-router-dom';\nimport { EntityTabsPanel } from './EntityTabsPanel';\nimport { EntityTabsList } from './EntityTabsList';\nimport { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha';\n\ntype SubRoute = {\n group?: string;\n path: string;\n title: string;\n icon?: string | ReactElement;\n children: JSX.Element;\n};\n\nexport function useSelectedSubRoute(subRoutes: SubRoute[]): {\n index: number;\n route?: SubRoute;\n element?: JSX.Element;\n} {\n const params = useParams();\n\n // For v7_relativeSplatPath: convert splat paths to parent/child structure\n const routes = subRoutes.map(({ path, children }) => ({\n caseSensitive: false,\n path: path,\n element: <Outlet />,\n children: [\n {\n index: true,\n element: children,\n },\n {\n path: '*',\n element: children,\n },\n ],\n }));\n\n // Sort routes by path length (longest first) for proper matching\n const sortedRoutes = routes.sort((a, b) => b.path.localeCompare(a.path));\n\n const element = useRoutes(sortedRoutes) ?? subRoutes[0]?.children;\n\n // TODO(Rugvip): Once we only support v6 stable we can always prefix\n // This avoids having a double / prefix for react-router v6 beta, which in turn breaks\n // the tab highlighting when using relative paths for the tabs.\n let currentRoute = params['*'] ?? '';\n if (!currentRoute.startsWith('/')) {\n currentRoute = `/${currentRoute}`;\n }\n\n const [matchedRoute] = matchRoutes(sortedRoutes, currentRoute) ?? [];\n const foundIndex = matchedRoute\n ? subRoutes.findIndex(t => t.path === matchedRoute.route.path)\n : 0;\n\n return {\n index: foundIndex === -1 ? 0 : foundIndex,\n element,\n route: subRoutes[foundIndex] ?? subRoutes[0],\n };\n}\n\ntype EntityTabsProps = {\n routes: SubRoute[];\n groupDefinitions: EntityContentGroupDefinitions;\n showIcons?: boolean;\n};\n\nexport function EntityTabs(props: EntityTabsProps) {\n const { routes, groupDefinitions, showIcons } = props;\n\n const { index, route, element } = useSelectedSubRoute(routes);\n\n const tabs = useMemo(\n () =>\n routes.map(t => {\n const { path, title, group, icon } = t;\n let to = path;\n // Remove trailing /*\n to = to.replace(/\\/\\*$/, '');\n // And remove leading / for relative navigation\n to = to.replace(/^\\//, '');\n return {\n group,\n id: path,\n path: to,\n label: title,\n icon,\n };\n }),\n [routes],\n );\n\n return (\n <>\n <EntityTabsList\n tabs={tabs}\n selectedIndex={index}\n showIcons={showIcons}\n groupDefinitions={groupDefinitions}\n />\n <EntityTabsPanel>\n <Helmet title={route?.title} />\n {element}\n </EntityTabsPanel>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,19 @@
1
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { forwardRef, useState } from 'react';
3
3
  import { Link } from 'react-router-dom';
4
4
  import classnames from 'classnames';
5
5
  import Typography from '@material-ui/core/Typography';
6
- import ButtonBase from '@material-ui/core/ButtonBase';
7
6
  import Popover from '@material-ui/core/Popover';
8
7
  import { capitalize } from '@material-ui/core/utils';
9
8
  import { withStyles, createStyles } from '@material-ui/core/styles';
10
9
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10
+ import Button from '@material-ui/core/Button';
11
+ import ListItem from '@material-ui/core/ListItem';
12
+ import ListItemIcon from '@material-ui/core/ListItemIcon';
13
+ import ListItemText from '@material-ui/core/ListItemText';
14
+ import List from '@material-ui/core/List';
15
+ import { useApi } from '@backstage/core-plugin-api';
16
+ import { iconsApiRef } from '@backstage/frontend-plugin-api';
11
17
 
12
18
  const styles = (theme) => createStyles({
13
19
  /* Styles applied to the root element. */
@@ -30,9 +36,6 @@ const styles = (theme) => createStyles({
30
36
  minWidth: 160
31
37
  }
32
38
  },
33
- popInButton: {
34
- width: "100%"
35
- },
36
39
  defaultTab: {
37
40
  ...theme.typography.caption,
38
41
  padding: theme.spacing(3, 3),
@@ -112,8 +115,22 @@ const styles = (theme) => createStyles({
112
115
  flexDirection: "row"
113
116
  }
114
117
  });
118
+ function resolveIcon(icon, iconsApi, showIcons) {
119
+ if (!showIcons) {
120
+ return void 0;
121
+ }
122
+ if (typeof icon === "string") {
123
+ const Icon = iconsApi.getIcon(icon);
124
+ if (Icon) {
125
+ return /* @__PURE__ */ jsx(Icon, {});
126
+ }
127
+ return void 0;
128
+ }
129
+ return icon;
130
+ }
115
131
  const Tab = forwardRef(function Tab2(props, ref) {
116
132
  const [anchorEl, setAnchorEl] = useState(null);
133
+ const iconsApi = useApi(iconsApiRef);
117
134
  const open = Boolean(anchorEl);
118
135
  const submenuId = open ? "tabbed-submenu" : void 0;
119
136
  const {
@@ -123,15 +140,16 @@ const Tab = forwardRef(function Tab2(props, ref) {
123
140
  disableFocusRipple = false,
124
141
  items,
125
142
  fullWidth,
126
- icon,
127
143
  indicator,
128
144
  label,
129
145
  onSelectTab,
130
146
  selected,
131
147
  textColor = "inherit",
132
148
  wrapped = false,
133
- highlightedButton
149
+ highlightedButton,
150
+ showIcons = false
134
151
  } = props;
152
+ const groupIcon = resolveIcon(props.icon, iconsApi, showIcons);
135
153
  const testId = "data-testid" in props && props["data-testid"];
136
154
  const handleMenuClose = () => {
137
155
  setAnchorEl(null);
@@ -145,30 +163,24 @@ const Tab = forwardRef(function Tab2(props, ref) {
145
163
  classes && {
146
164
  [classes.disabled]: disabled,
147
165
  [classes.selected]: selected,
148
- [classes.labelIcon]: label && icon,
166
+ [classes.labelIcon]: label && groupIcon,
149
167
  [classes.fullWidth]: fullWidth,
150
168
  [classes.wrapped]: wrapped
151
169
  },
152
170
  className
153
171
  ];
154
- const innerButtonClasses = [
155
- classes?.root,
156
- classes?.[`textColor${capitalize(textColor)}`],
157
- classes?.defaultTab,
158
- classes && {
159
- [classes.disabled]: disabled,
160
- [classes.labelIcon]: label && icon,
161
- [classes.fullWidth]: fullWidth,
162
- [classes.wrapped]: wrapped
163
- }
164
- ];
165
172
  if (items.length === 1) {
166
173
  return /* @__PURE__ */ jsxs(
167
- ButtonBase,
174
+ Button,
168
175
  {
169
176
  focusRipple: !disableFocusRipple,
170
177
  "data-testid": testId,
171
- className: classnames(classArray),
178
+ className: classnames(
179
+ classArray,
180
+ classes && {
181
+ [classes.labelIcon]: label && (items[0].icon ?? groupIcon)
182
+ }
183
+ ),
172
184
  ref,
173
185
  role: "tab",
174
186
  "aria-selected": selected,
@@ -176,19 +188,18 @@ const Tab = forwardRef(function Tab2(props, ref) {
176
188
  component: Link,
177
189
  onClick: onSelectTab,
178
190
  to: items[0]?.path,
191
+ startIcon: resolveIcon(items[0].icon, iconsApi, showIcons),
179
192
  children: [
180
- /* @__PURE__ */ jsxs(Typography, { className: classes?.wrapper, variant: "button", children: [
181
- icon,
182
- items[0].label
183
- ] }),
193
+ /* @__PURE__ */ jsx(Typography, { className: classes?.wrapper, variant: "button", children: items[0].label }),
184
194
  indicator
185
195
  ]
186
196
  }
187
197
  );
188
198
  }
199
+ const hasIcons = showIcons && items.some((i) => i.icon);
189
200
  return /* @__PURE__ */ jsxs(Fragment, { children: [
190
201
  /* @__PURE__ */ jsxs(
191
- ButtonBase,
202
+ Button,
192
203
  {
193
204
  "data-testid": testId,
194
205
  focusRipple: !disableFocusRipple,
@@ -198,6 +209,7 @@ const Tab = forwardRef(function Tab2(props, ref) {
198
209
  "aria-selected": selected,
199
210
  disabled,
200
211
  onClick: handleMenuClick,
212
+ startIcon: groupIcon,
201
213
  children: [
202
214
  /* @__PURE__ */ jsx(Typography, { className: classes?.wrapper, variant: "button", children: label }),
203
215
  /* @__PURE__ */ jsx(ExpandMoreIcon, {})
@@ -219,33 +231,45 @@ const Tab = forwardRef(function Tab2(props, ref) {
219
231
  vertical: "top",
220
232
  horizontal: "center"
221
233
  },
222
- children: items.map((i, idx) => /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
223
- ButtonBase,
224
- {
225
- focusRipple: !disableFocusRipple,
226
- className: classnames(
227
- innerButtonClasses,
228
- classes?.popInButton,
229
- highlightedButton === i.index ? classes?.selectedButton : classes?.unselectedButton
230
- ),
231
- ref,
232
- "aria-selected": selected,
233
- disabled,
234
- component: Link,
235
- onClick: (e) => {
236
- handleMenuClose();
237
- onSelectTab(e);
234
+ children: /* @__PURE__ */ jsx(List, { component: "nav", children: items.map((i) => {
235
+ const itemIcon = resolveIcon(i.icon, iconsApi, showIcons);
236
+ return /* @__PURE__ */ jsxs(
237
+ ListItem,
238
+ {
239
+ button: true,
240
+ focusRipple: !disableFocusRipple,
241
+ classes: {
242
+ selected: classnames(classes?.selectedButton),
243
+ default: classnames(classes?.unselectedButton),
244
+ disabled: classnames(classes?.disabled)
245
+ },
246
+ ref,
247
+ "aria-selected": selected,
248
+ disabled,
249
+ selected: highlightedButton === i.id,
250
+ component: Link,
251
+ onClick: (e) => {
252
+ handleMenuClose();
253
+ onSelectTab?.(e);
254
+ },
255
+ to: i.path,
256
+ children: [
257
+ itemIcon && /* @__PURE__ */ jsx(ListItemIcon, { children: itemIcon }),
258
+ /* @__PURE__ */ jsx(
259
+ ListItemText,
260
+ {
261
+ inset: !itemIcon && hasIcons,
262
+ primary: /* @__PURE__ */ jsxs(Fragment, { children: [
263
+ /* @__PURE__ */ jsx(Typography, { variant: "button", children: i.label }),
264
+ indicator
265
+ ] })
266
+ }
267
+ )
268
+ ]
238
269
  },
239
- to: i.path,
240
- children: [
241
- /* @__PURE__ */ jsxs(Typography, { className: classes?.wrapper, variant: "button", children: [
242
- icon,
243
- i.label
244
- ] }),
245
- indicator
246
- ]
247
- }
248
- ) }, `popover_item_${idx}`))
270
+ `popover_item_${i.id}`
271
+ );
272
+ }) })
249
273
  }
250
274
  )
251
275
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTabsGroup.esm.js","sources":["../../../../src/alpha/components/EntityTabs/EntityTabsGroup.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n ReactNode,\n forwardRef,\n useState,\n MouseEvent,\n MouseEventHandler,\n} from 'react';\nimport { Link } from 'react-router-dom';\nimport classnames from 'classnames';\n\nimport Typography from '@material-ui/core/Typography';\nimport ButtonBase from '@material-ui/core/ButtonBase';\nimport Popover from '@material-ui/core/Popover';\nimport { TabProps, TabClassKey } from '@material-ui/core/Tab';\nimport { capitalize } from '@material-ui/core/utils';\nimport { createStyles, Theme, withStyles } from '@material-ui/core/styles';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\n\nconst styles = (theme: Theme) =>\n createStyles({\n /* Styles applied to the root element. */\n root: {\n ...theme.typography.button,\n maxWidth: 264,\n minWidth: 72,\n position: 'relative',\n boxSizing: 'border-box',\n minHeight: 48,\n flexShrink: 0,\n padding: '6px 12px',\n [theme.breakpoints.up('sm')]: {\n padding: '6px 24px',\n },\n overflow: 'hidden',\n whiteSpace: 'normal',\n textAlign: 'center',\n [theme.breakpoints.up('sm')]: {\n minWidth: 160,\n },\n },\n popInButton: {\n width: '100%',\n },\n defaultTab: {\n ...theme.typography.caption,\n padding: theme.spacing(3, 3),\n textTransform: 'uppercase',\n fontWeight: theme.typography.fontWeightBold,\n color: theme.palette.text.secondary,\n },\n /* Styles applied to the root element if both `icon` and `label` are provided. */\n labelIcon: {\n minHeight: 72,\n paddingTop: 9,\n '& $wrapper > *:first-child': {\n marginBottom: 6,\n },\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"inherit\"`. */\n textColorInherit: {\n color: 'inherit',\n opacity: 0.7,\n '&$selected': {\n opacity: 1,\n },\n '&$disabled': {\n opacity: 0.5,\n },\n },\n selectedButton: {\n color: `${theme.palette.text.primary}`,\n opacity: `${1}`,\n },\n unselectedButton: {\n color: `${theme.palette.text.secondary}`,\n opacity: `${0.7}`,\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"primary\"`. */\n textColorPrimary: {\n color: theme.palette.text.secondary,\n '&$selected': {\n color: theme.palette.primary.main,\n },\n '&$disabled': {\n color: theme.palette.text.disabled,\n },\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"secondary\"`. */\n textColorSecondary: {\n color: theme.palette.text.secondary,\n '&$selected': {\n color: theme.palette.secondary.main,\n },\n '&$disabled': {\n color: theme.palette.text.disabled,\n },\n },\n /* Pseudo-class applied to the root element if `selected={true}` (controlled by the Tabs component). */\n selected: {},\n /* Pseudo-class applied to the root element if `disabled={true}` (controlled by the Tabs component). */\n disabled: {},\n /* Styles applied to the root element if `fullWidth={true}` (controlled by the Tabs component). */\n fullWidth: {\n flexShrink: 1,\n flexGrow: 1,\n flexBasis: 0,\n maxWidth: 'none',\n },\n /* Styles applied to the root element if `wrapped={true}`. */\n wrapped: {\n fontSize: theme.typography.pxToRem(12),\n lineHeight: 1.5,\n },\n /* Styles applied to the `icon` and `label`'s wrapper element. */\n wrapper: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n flexDirection: 'row',\n },\n });\n\ntype EntityTabsGroupItem = {\n id: string;\n index: number;\n label: string;\n path: string;\n group: string;\n};\n\ntype EntityTabsGroupProps = TabProps & {\n classes?: Partial<ReturnType<typeof styles>>;\n indicator?: ReactNode;\n highlightedButton?: number;\n items: EntityTabsGroupItem[];\n onSelectTab: MouseEventHandler<HTMLAnchorElement>;\n};\n\nconst Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) {\n const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);\n\n const open = Boolean(anchorEl);\n const submenuId = open ? 'tabbed-submenu' : undefined;\n\n const {\n classes,\n className,\n disabled = false,\n disableFocusRipple = false,\n items,\n fullWidth,\n icon,\n indicator,\n label,\n onSelectTab,\n selected,\n textColor = 'inherit',\n wrapped = false,\n highlightedButton,\n } = props;\n\n const testId = 'data-testid' in props && props['data-testid'];\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleMenuClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const classArray = [\n classes?.root,\n classes?.[`textColor${capitalize(textColor)}` as TabClassKey],\n classes && {\n [classes.disabled!]: disabled,\n [classes.selected!]: selected,\n [classes.labelIcon!]: label && icon,\n [classes.fullWidth!]: fullWidth,\n [classes.wrapped!]: wrapped,\n },\n className,\n ];\n\n const innerButtonClasses = [\n classes?.root,\n classes?.[`textColor${capitalize(textColor)}` as TabClassKey],\n classes?.defaultTab,\n classes && {\n [classes.disabled!]: disabled,\n [classes.labelIcon!]: label && icon,\n [classes.fullWidth!]: fullWidth,\n [classes.wrapped!]: wrapped,\n },\n ];\n\n if (items.length === 1) {\n return (\n <ButtonBase\n focusRipple={!disableFocusRipple}\n data-testid={testId}\n className={classnames(classArray)}\n ref={ref}\n role=\"tab\"\n aria-selected={selected}\n disabled={disabled}\n component={Link}\n onClick={onSelectTab}\n to={items[0]?.path}\n >\n <Typography className={classes?.wrapper} variant=\"button\">\n {icon}\n {items[0].label}\n </Typography>\n {indicator}\n </ButtonBase>\n );\n }\n return (\n <>\n <ButtonBase\n data-testid={testId}\n focusRipple={!disableFocusRipple}\n className={classnames(classArray)}\n ref={ref}\n role=\"tab\"\n aria-selected={selected}\n disabled={disabled}\n onClick={handleMenuClick}\n >\n <Typography className={classes?.wrapper} variant=\"button\">\n {label}\n </Typography>\n <ExpandMoreIcon />\n </ButtonBase>\n <Popover\n id={submenuId}\n open={open}\n anchorEl={anchorEl}\n onClose={handleMenuClose}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'center',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'center',\n }}\n >\n {items.map((i, idx) => (\n <div key={`popover_item_${idx}`}>\n <ButtonBase\n focusRipple={!disableFocusRipple}\n className={classnames(\n innerButtonClasses,\n classes?.popInButton,\n highlightedButton === i.index\n ? classes?.selectedButton\n : classes?.unselectedButton,\n )}\n ref={ref}\n aria-selected={selected}\n disabled={disabled}\n component={Link}\n onClick={e => {\n handleMenuClose();\n onSelectTab(e);\n }}\n to={i.path}\n >\n <Typography className={classes?.wrapper} variant=\"button\">\n {icon}\n {i.label}\n </Typography>\n {indicator}\n </ButtonBase>\n </div>\n ))}\n </Popover>\n </>\n );\n});\n\n// @ts-ignore\nexport const EntityTabsGroup = withStyles(styles, { name: 'MuiTab' })(Tab);\n"],"names":["Tab"],"mappings":"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"}
1
+ {"version":3,"file":"EntityTabsGroup.esm.js","sources":["../../../../src/alpha/components/EntityTabs/EntityTabsGroup.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n ReactNode,\n forwardRef,\n useState,\n MouseEvent,\n MouseEventHandler,\n ReactElement,\n} from 'react';\nimport { Link } from 'react-router-dom';\nimport classnames from 'classnames';\n\nimport Typography from '@material-ui/core/Typography';\nimport Popover from '@material-ui/core/Popover';\nimport { TabProps, TabClassKey } from '@material-ui/core/Tab';\nimport { capitalize } from '@material-ui/core/utils';\nimport { createStyles, Theme, withStyles } from '@material-ui/core/styles';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport Button from '@material-ui/core/Button';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport List from '@material-ui/core/List';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { IconsApi, iconsApiRef } from '@backstage/frontend-plugin-api';\n\nconst styles = (theme: Theme) =>\n createStyles({\n /* Styles applied to the root element. */\n root: {\n ...theme.typography.button,\n maxWidth: 264,\n minWidth: 72,\n position: 'relative',\n boxSizing: 'border-box',\n minHeight: 48,\n flexShrink: 0,\n padding: '6px 12px',\n [theme.breakpoints.up('sm')]: {\n padding: '6px 24px',\n },\n overflow: 'hidden',\n whiteSpace: 'normal',\n textAlign: 'center',\n [theme.breakpoints.up('sm')]: {\n minWidth: 160,\n },\n },\n defaultTab: {\n ...theme.typography.caption,\n padding: theme.spacing(3, 3),\n textTransform: 'uppercase',\n fontWeight: theme.typography.fontWeightBold,\n color: theme.palette.text.secondary,\n },\n /* Styles applied to the root element if both `icon` and `label` are provided. */\n labelIcon: {\n minHeight: 72,\n paddingTop: 9,\n '& $wrapper > *:first-child': {\n marginBottom: 6,\n },\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"inherit\"`. */\n textColorInherit: {\n color: 'inherit',\n opacity: 0.7,\n '&$selected': {\n opacity: 1,\n },\n '&$disabled': {\n opacity: 0.5,\n },\n },\n selectedButton: {\n color: `${theme.palette.text.primary}`,\n opacity: `${1}`,\n },\n unselectedButton: {\n color: `${theme.palette.text.secondary}`,\n opacity: `${0.7}`,\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"primary\"`. */\n textColorPrimary: {\n color: theme.palette.text.secondary,\n '&$selected': {\n color: theme.palette.primary.main,\n },\n '&$disabled': {\n color: theme.palette.text.disabled,\n },\n },\n /* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor=\"secondary\"`. */\n textColorSecondary: {\n color: theme.palette.text.secondary,\n '&$selected': {\n color: theme.palette.secondary.main,\n },\n '&$disabled': {\n color: theme.palette.text.disabled,\n },\n },\n /* Pseudo-class applied to the root element if `selected={true}` (controlled by the Tabs component). */\n selected: {},\n /* Pseudo-class applied to the root element if `disabled={true}` (controlled by the Tabs component). */\n disabled: {},\n /* Styles applied to the root element if `fullWidth={true}` (controlled by the Tabs component). */\n fullWidth: {\n flexShrink: 1,\n flexGrow: 1,\n flexBasis: 0,\n maxWidth: 'none',\n },\n /* Styles applied to the root element if `wrapped={true}`. */\n wrapped: {\n fontSize: theme.typography.pxToRem(12),\n lineHeight: 1.5,\n },\n /* Styles applied to the `icon` and `label`'s wrapper element. */\n wrapper: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n flexDirection: 'row',\n },\n });\n\ntype EntityTabsGroupItem = {\n id: string;\n label: string;\n path: string;\n icon?: string | ReactElement;\n};\n\ntype EntityTabsGroupProps = TabProps & {\n classes?: Partial<ReturnType<typeof styles>>;\n indicator?: ReactNode;\n highlightedButton?: string;\n items: EntityTabsGroupItem[];\n onSelectTab?: MouseEventHandler<HTMLAnchorElement>;\n showIcons?: boolean;\n};\n\nfunction resolveIcon(\n icon: string | ReactElement | undefined,\n iconsApi: IconsApi,\n showIcons: boolean,\n) {\n if (!showIcons) {\n return undefined;\n }\n if (typeof icon === 'string') {\n const Icon = iconsApi.getIcon(icon);\n if (Icon) {\n return <Icon />;\n }\n return undefined;\n }\n return icon;\n}\n\nconst Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) {\n const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);\n const iconsApi = useApi(iconsApiRef);\n\n const open = Boolean(anchorEl);\n const submenuId = open ? 'tabbed-submenu' : undefined;\n\n const {\n classes,\n className,\n disabled = false,\n disableFocusRipple = false,\n items,\n fullWidth,\n indicator,\n label,\n onSelectTab,\n selected,\n textColor = 'inherit',\n wrapped = false,\n highlightedButton,\n showIcons = false,\n } = props;\n\n const groupIcon = resolveIcon(props.icon, iconsApi, showIcons);\n const testId = 'data-testid' in props && props['data-testid'];\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleMenuClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const classArray = [\n classes?.root,\n classes?.[`textColor${capitalize(textColor)}` as TabClassKey],\n classes && {\n [classes.disabled!]: disabled,\n [classes.selected!]: selected,\n [classes.labelIcon!]: label && groupIcon,\n [classes.fullWidth!]: fullWidth,\n [classes.wrapped!]: wrapped,\n },\n className,\n ];\n\n if (items.length === 1) {\n return (\n <Button\n focusRipple={!disableFocusRipple}\n data-testid={testId}\n className={classnames(\n classArray,\n classes && {\n [classes.labelIcon!]: label && (items[0].icon ?? groupIcon),\n },\n )}\n ref={ref}\n role=\"tab\"\n aria-selected={selected}\n disabled={disabled}\n component={Link}\n onClick={onSelectTab}\n to={items[0]?.path}\n startIcon={resolveIcon(items[0].icon, iconsApi, showIcons)}\n >\n <Typography className={classes?.wrapper} variant=\"button\">\n {items[0].label}\n </Typography>\n {indicator}\n </Button>\n );\n }\n const hasIcons = showIcons && items.some(i => i.icon);\n return (\n <>\n <Button\n data-testid={testId}\n focusRipple={!disableFocusRipple}\n className={classnames(classArray)}\n ref={ref}\n role=\"tab\"\n aria-selected={selected}\n disabled={disabled}\n onClick={handleMenuClick}\n startIcon={groupIcon}\n >\n <Typography className={classes?.wrapper} variant=\"button\">\n {label}\n </Typography>\n <ExpandMoreIcon />\n </Button>\n <Popover\n id={submenuId}\n open={open}\n anchorEl={anchorEl}\n onClose={handleMenuClose}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'center',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'center',\n }}\n >\n <List component=\"nav\">\n {items.map(i => {\n const itemIcon = resolveIcon(i.icon, iconsApi, showIcons);\n return (\n <ListItem\n key={`popover_item_${i.id}`}\n button\n focusRipple={!disableFocusRipple}\n classes={{\n selected: classnames(classes?.selectedButton),\n default: classnames(classes?.unselectedButton),\n disabled: classnames(classes?.disabled),\n }}\n ref={ref}\n aria-selected={selected}\n disabled={disabled}\n selected={highlightedButton === i.id}\n component={Link}\n onClick={e => {\n handleMenuClose();\n onSelectTab?.(e);\n }}\n to={i.path}\n >\n {itemIcon && <ListItemIcon>{itemIcon}</ListItemIcon>}\n <ListItemText\n inset={!itemIcon && hasIcons}\n primary={\n <>\n <Typography variant=\"button\">{i.label}</Typography>\n {indicator}\n </>\n }\n />\n </ListItem>\n );\n })}\n </List>\n </Popover>\n </>\n );\n});\n\n// @ts-ignore\nexport const EntityTabsGroup = withStyles(styles, { name: 'MuiTab' })(Tab);\n"],"names":["Tab"],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { useMemo, useState, useCallback, useEffect } from 'react';
2
+ import { useMemo } from 'react';
3
3
  import Box from '@material-ui/core/Box';
4
4
  import Tabs from '@material-ui/core/Tabs';
5
5
  import { makeStyles } from '@material-ui/core/styles';
6
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
6
7
  import { EntityTabsGroup } from './EntityTabsGroup.esm.js';
8
+ import { catalogTranslationRef } from '../../translation.esm.js';
7
9
 
8
10
  const useStyles = makeStyles(
9
11
  (theme) => ({
@@ -34,24 +36,22 @@ const useStyles = makeStyles(
34
36
  );
35
37
  function EntityTabsList(props) {
36
38
  const styles = useStyles();
37
- const { tabs: items, onChange, selectedIndex: selectedItem = 0 } = props;
39
+ const { t } = useTranslationRef(catalogTranslationRef);
40
+ const { tabs: items, selectedIndex = 0, showIcons, groupDefinitions } = props;
38
41
  const groups = useMemo(
39
- () => [...new Set(items.map((item) => item.group))],
40
- [items]
42
+ () => items.reduce((result, tab) => {
43
+ const group = tab.group ? groupDefinitions[tab.group] : void 0;
44
+ const groupOrId = group && tab.group ? tab.group : tab.id;
45
+ result[groupOrId] = result[groupOrId] ?? {
46
+ group,
47
+ items: []
48
+ };
49
+ result[groupOrId].items.push(tab);
50
+ return result;
51
+ }, {}),
52
+ [items, groupDefinitions]
41
53
  );
42
- const [selectedGroup, setSelectedGroup] = useState(
43
- selectedItem && items[selectedItem] ? groups.indexOf(items[selectedItem].group) : 0
44
- );
45
- const handleChange = useCallback(
46
- (index) => {
47
- if (selectedItem !== index) onChange?.(index);
48
- },
49
- [selectedItem, onChange]
50
- );
51
- useEffect(() => {
52
- if (selectedItem === void 0 || !items[selectedItem]) return;
53
- setSelectedGroup(groups.indexOf(items[selectedItem].group));
54
- }, [items, selectedItem, groups, setSelectedGroup]);
54
+ const selectedItem = items[selectedIndex];
55
55
  return /* @__PURE__ */ jsx(Box, { className: styles.tabsWrapper, children: /* @__PURE__ */ jsx(
56
56
  Tabs,
57
57
  {
@@ -60,33 +60,23 @@ function EntityTabsList(props) {
60
60
  textColor: "inherit",
61
61
  variant: "scrollable",
62
62
  scrollButtons: "auto",
63
- "aria-label": "tabs",
64
- value: selectedGroup,
65
- children: groups.map((group, groupIndex) => {
66
- const groupItems = [];
67
- items.forEach((item, itemIndex) => {
68
- if (item.group === group) {
69
- groupItems.push({
70
- ...item,
71
- index: itemIndex
72
- });
73
- }
74
- });
75
- return /* @__PURE__ */ jsx(
76
- EntityTabsGroup,
77
- {
78
- "data-testid": `header-tab-${groupIndex}`,
79
- className: styles.defaultTab,
80
- classes: { selected: styles.selected, root: styles.tabRoot },
81
- label: group,
82
- value: groupIndex,
83
- items: groupItems,
84
- highlightedButton: selectedItem,
85
- onSelectTab: () => handleChange(groupIndex)
86
- },
87
- group
88
- );
89
- })
63
+ "aria-label": t("entityTabs.tabsAriaLabel"),
64
+ value: selectedItem?.group ?? selectedItem?.id,
65
+ children: Object.entries(groups).map(([id, tabGroup]) => /* @__PURE__ */ jsx(
66
+ EntityTabsGroup,
67
+ {
68
+ "data-testid": `header-tab-${id}`,
69
+ className: styles.defaultTab,
70
+ classes: { selected: styles.selected, root: styles.tabRoot },
71
+ label: tabGroup.group?.title,
72
+ icon: tabGroup.group?.icon,
73
+ value: id,
74
+ items: tabGroup.items,
75
+ highlightedButton: selectedItem?.id,
76
+ showIcons
77
+ },
78
+ id
79
+ ))
90
80
  }
91
81
  ) });
92
82
  }