@nospt/backstage-plugin-apigee 1.1.0-rc3 → 1.1.0-rc5

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 (48) hide show
  1. package/config.d.ts +0 -10
  2. package/dist/components/ApiCard.esm.js +61 -47
  3. package/dist/components/ApiCard.esm.js.map +1 -1
  4. package/dist/components/ApiCard.module.css.esm.js +2 -2
  5. package/dist/components/ApiCardGrid.esm.js +6 -34
  6. package/dist/components/ApiCardGrid.esm.js.map +1 -1
  7. package/dist/components/ApiDetailsDrawer.esm.js +335 -0
  8. package/dist/components/ApiDetailsDrawer.esm.js.map +1 -0
  9. package/dist/components/ApiDetailsDrawer.module.css.esm.js +8 -0
  10. package/dist/components/ApiDetailsDrawer.module.css.esm.js.map +1 -0
  11. package/dist/components/ApiTable.esm.js +170 -0
  12. package/dist/components/ApiTable.esm.js.map +1 -0
  13. package/dist/components/ApiTable.module.css.esm.js +8 -0
  14. package/dist/components/ApiTable.module.css.esm.js.map +1 -0
  15. package/dist/components/ApigeeApiCatalogPage.esm.js +98 -79
  16. package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -1
  17. package/dist/components/CountCards.esm.js +23 -0
  18. package/dist/components/CountCards.esm.js.map +1 -1
  19. package/dist/components/FilterToolbar.esm.js +43 -39
  20. package/dist/components/FilterToolbar.esm.js.map +1 -1
  21. package/dist/components/FilterToolbar.module.css.esm.js +2 -2
  22. package/dist/components/apiCardViewModel.esm.js +16 -3
  23. package/dist/components/apiCardViewModel.esm.js.map +1 -1
  24. package/dist/components/apiDetailViewModel.esm.js +60 -0
  25. package/dist/components/apiDetailViewModel.esm.js.map +1 -0
  26. package/dist/components/apigeeFacetFilters.esm.js +3 -6
  27. package/dist/components/apigeeFacetFilters.esm.js.map +1 -1
  28. package/dist/components/apigeeSearchParams.esm.js +0 -1
  29. package/dist/components/apigeeSearchParams.esm.js.map +1 -1
  30. package/dist/components/facetModel.esm.js +3 -29
  31. package/dist/components/facetModel.esm.js.map +1 -1
  32. package/dist/components/grouping.esm.js +1 -26
  33. package/dist/components/grouping.esm.js.map +1 -1
  34. package/dist/components/useApigeeFilters.esm.js +1 -3
  35. package/dist/components/useApigeeFilters.esm.js.map +1 -1
  36. package/dist/constants.esm.js +3 -3
  37. package/dist/constants.esm.js.map +1 -1
  38. package/dist/index.d.ts +52 -1
  39. package/dist/index.esm.js +1 -1
  40. package/dist/plugin.esm.js +17 -7
  41. package/dist/plugin.esm.js.map +1 -1
  42. package/dist/routes.esm.js +2 -1
  43. package/dist/routes.esm.js.map +1 -1
  44. package/package.json +2 -2
  45. package/dist/components/ApiCardGrid.module.css.esm.js +0 -8
  46. package/dist/components/ApiCardGrid.module.css.esm.js.map +0 -1
  47. package/dist/components/categoryIcons.esm.js +0 -53
  48. package/dist/components/categoryIcons.esm.js.map +0 -1
package/config.d.ts CHANGED
@@ -2,16 +2,6 @@
2
2
  export interface Config {
3
3
  apigee?: {
4
4
  catalog?: {
5
- /**
6
- * Optional override of the default category -> icon map (UI-10 / D-08).
7
- * Keys are category slugs (matching the apigee.com/category annotation);
8
- * values are icon KEYS from the fixed allow-list resolved by
9
- * src/components/categoryIcons.tsx. Unknown/disallowed values fall back to
10
- * the `Category` icon (a value is NEVER a module path or component — see
11
- * Registry Safety in 05-UI-SPEC.md).
12
- * @visibility frontend
13
- */
14
- categoryIcons?: { [categorySlug: string]: string };
15
5
  /**
16
6
  * Optional org-id -> display-label map for the Org facet.
17
7
  * Keys are raw annotation values (for example `apigee-x-private-p-495866`);
@@ -1,83 +1,97 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { Link, StatusOK, StatusError, StatusWarning } from '@backstage/core-components';
2
+ import { Link } from '@backstage/core-components';
3
3
  import { useRouteRef } from '@backstage/frontend-plugin-api';
4
4
  import { entityRouteRef } from '@backstage/plugin-catalog-react';
5
5
  import { Text } from '@backstage/ui';
6
- import { RiTeamLine, RiFileTextLine } from '@remixicon/react';
6
+ import { RiGlobalLine, RiLockLine, RiTeamLine, RiBuildingLine, RiFileTextLine } from '@remixicon/react';
7
7
  import styles from './ApiCard.module.css.esm.js';
8
8
 
9
+ const BADGE_KIND_CLASS = {
10
+ ok: styles.badgeOk,
11
+ warning: styles.badgeWarning,
12
+ error: styles.badgeError
13
+ };
14
+ const VISIBILITY_ICON = {
15
+ private: RiLockLine,
16
+ public: RiGlobalLine
17
+ };
9
18
  function LifecycleBadge({
10
19
  kind,
11
20
  label
12
21
  }) {
13
- if (kind === "ok") {
14
- return /* @__PURE__ */ jsx(StatusOK, { children: label });
15
- }
16
- if (kind === "error") {
17
- return /* @__PURE__ */ jsx(StatusError, { children: label });
18
- }
19
- return /* @__PURE__ */ jsx(StatusWarning, { children: label });
22
+ return /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${BADGE_KIND_CLASS[kind]}`, children: label });
20
23
  }
21
- function ApiCard({ vm, icon: Icon }) {
24
+ function ApiCard({ vm, onSelect }) {
22
25
  const entityRoute = useRouteRef(entityRouteRef);
23
26
  const { kind, namespace, name } = vm.targetRef;
24
27
  const href = entityRoute ? entityRoute({ kind: kind.toLowerCase(), namespace, name }) : void 0;
25
28
  const ariaLabel = `Open ${vm.name} API details`;
26
29
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
27
30
  /* @__PURE__ */ jsxs("div", { className: styles.header, children: [
28
- Icon ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: styles.categoryIcon, size: 20 }) : null,
29
- /* @__PURE__ */ jsx(
31
+ /* @__PURE__ */ jsx("div", { className: styles.titleRow, children: /* @__PURE__ */ jsx(
30
32
  Text,
31
33
  {
32
- variant: "body-medium",
34
+ variant: "body-large",
33
35
  weight: "bold",
34
36
  as: "h3",
35
37
  className: styles.name,
36
38
  children: vm.name
37
39
  }
38
- ),
39
- /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label })
40
- ] }),
41
- /* @__PURE__ */ jsxs("div", { className: styles.meta, children: [
42
- /* @__PURE__ */ jsx(RiTeamLine, { "aria-hidden": true, size: 16 }),
43
- /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: vm.ownerLabel }),
44
- vm.version ? /* @__PURE__ */ jsxs(
40
+ ) }),
41
+ /* @__PURE__ */ jsxs("div", { className: styles.badges, children: [
42
+ /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label }),
43
+ vm.version ? /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${styles.badgeVersion}`, children: vm.version }) : null
44
+ ] }),
45
+ vm.description ? /* @__PURE__ */ jsx(
45
46
  Text,
46
47
  {
47
48
  variant: "body-small",
48
49
  color: "secondary",
49
- as: "span",
50
- className: styles.version,
51
- children: [
52
- "v",
53
- vm.version
54
- ]
50
+ as: "p",
51
+ className: styles.description,
52
+ children: vm.description
55
53
  }
56
54
  ) : null
57
55
  ] }),
58
- vm.description ? /* @__PURE__ */ jsx(
59
- Text,
60
- {
61
- variant: "body-small",
62
- color: "secondary",
63
- as: "p",
64
- className: styles.description,
65
- children: vm.description
66
- }
67
- ) : null,
68
- vm.hasSpec ? /* @__PURE__ */ jsx("div", { className: styles.footer, children: /* @__PURE__ */ jsx(
69
- "span",
56
+ /* @__PURE__ */ jsxs("div", { className: styles.body, children: [
57
+ /* @__PURE__ */ jsxs("div", { className: styles.leftCol, children: [
58
+ /* @__PURE__ */ jsxs("div", { className: styles.metaItem, children: [
59
+ /* @__PURE__ */ jsx(RiTeamLine, { "aria-hidden": true, size: 16 }),
60
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: vm.ownerLabel })
61
+ ] }),
62
+ vm.department ? /* @__PURE__ */ jsxs("div", { className: styles.metaItem, children: [
63
+ /* @__PURE__ */ jsx(RiBuildingLine, { "aria-hidden": true, size: 16 }),
64
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: vm.department })
65
+ ] }) : null,
66
+ vm.visibilities.map((visibility) => {
67
+ const VisibilityIcon = VISIBILITY_ICON[visibility];
68
+ return /* @__PURE__ */ jsxs("div", { className: styles.metaItem, children: [
69
+ /* @__PURE__ */ jsx(VisibilityIcon, { "aria-hidden": true, size: 16 }),
70
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: visibility })
71
+ ] }, visibility);
72
+ }),
73
+ vm.hasSpec ? /* @__PURE__ */ jsxs("div", { className: styles.metaItem, children: [
74
+ /* @__PURE__ */ jsx(RiFileTextLine, { "aria-hidden": true, size: 16 }),
75
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: "OpenAPI spec" })
76
+ ] }) : null
77
+ ] }),
78
+ /* @__PURE__ */ jsx("div", { className: styles.rightCol })
79
+ ] })
80
+ ] });
81
+ if (onSelect) {
82
+ return /* @__PURE__ */ jsx(
83
+ "button",
70
84
  {
71
- role: "img",
72
- "aria-label": "OpenAPI spec available",
73
- title: "OpenAPI spec available",
74
- className: styles.specIcon,
75
- children: /* @__PURE__ */ jsx(RiFileTextLine, { "aria-hidden": true, size: 16 })
85
+ type: "button",
86
+ className: styles.card,
87
+ "aria-label": ariaLabel,
88
+ onClick: () => onSelect(vm),
89
+ children: content
76
90
  }
77
- ) }) : null
78
- ] });
79
- return /* @__PURE__ */ jsx("div", { className: styles.card, children: href ? /* @__PURE__ */ jsx(Link, { to: href, className: styles.action, "aria-label": ariaLabel, children: content }) : /* @__PURE__ */ jsx("div", { className: styles.action, children: content }) });
91
+ );
92
+ }
93
+ return href ? /* @__PURE__ */ jsx(Link, { to: href, className: styles.card, "aria-label": ariaLabel, children: content }) : /* @__PURE__ */ jsx("div", { className: styles.card, children: content });
80
94
  }
81
95
 
82
- export { ApiCard };
96
+ export { ApiCard, LifecycleBadge, VISIBILITY_ICON };
83
97
  //# sourceMappingURL=ApiCard.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ApiCard.esm.js","sources":["../../src/components/ApiCard.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport {\n Link,\n StatusError,\n StatusOK,\n StatusWarning,\n} from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\nimport { entityRouteRef } from '@backstage/plugin-catalog-react';\nimport { Text } from '@backstage/ui';\nimport {\n RiFileTextLine,\n RiTeamLine,\n type RemixiconComponentType,\n} from '@remixicon/react';\nimport type { ApiCardViewModel, LifecycleBadgeKind } from './apiCardViewModel';\nimport styles from './ApiCard.module.css';\n\nexport interface ApiCardProps {\n vm: ApiCardViewModel;\n /** Category icon resolved by the parent grid (05-05); decorative. */\n icon?: RemixiconComponentType;\n}\n\nfunction LifecycleBadge({\n kind,\n label,\n}: {\n kind: LifecycleBadgeKind;\n label: string;\n}) {\n if (kind === 'ok') {\n return <StatusOK>{label}</StatusOK>;\n }\n if (kind === 'error') {\n return <StatusError>{label}</StatusError>;\n }\n return <StatusWarning>{label}</StatusWarning>;\n}\n\n/**\n * A single Apigee API tile. The whole card is one anchor-based control whose href is\n * resolved via `entityRouteRef` (never hardcoded — TR04); it degrades to a non-link\n * button when the route ref is unbound (dev harness). Entity text renders as React\n * children only — never via raw HTML injection (T-05-03). (UI-02, UI-03)\n */\nexport function ApiCard({ vm, icon: Icon }: ApiCardProps) {\n const entityRoute = useRouteRef(entityRouteRef);\n const { kind, namespace, name } = vm.targetRef;\n const href = entityRoute\n ? entityRoute({ kind: kind.toLowerCase(), namespace, name })\n : undefined;\n const ariaLabel = `Open ${vm.name} API details`;\n\n const content = (\n <>\n <div className={styles.header}>\n {Icon ? (\n <Icon aria-hidden className={styles.categoryIcon} size={20} />\n ) : null}\n <Text\n variant=\"body-medium\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.name}\n >\n {vm.name}\n </Text>\n <LifecycleBadge kind={vm.badge.kind} label={vm.badge.label} />\n </div>\n\n <div className={styles.meta}>\n <RiTeamLine aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {vm.ownerLabel}\n </Text>\n {vm.version ? (\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n as=\"span\"\n className={styles.version}\n >\n v{vm.version}\n </Text>\n ) : null}\n </div>\n\n {vm.description ? (\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n as=\"p\"\n className={styles.description}\n >\n {vm.description}\n </Text>\n ) : null}\n\n {vm.hasSpec ? (\n <div className={styles.footer}>\n <span\n role=\"img\"\n aria-label=\"OpenAPI spec available\"\n title=\"OpenAPI spec available\"\n className={styles.specIcon}\n >\n <RiFileTextLine aria-hidden size={16} />\n </span>\n </div>\n ) : null}\n </>\n );\n\n return (\n <div className={styles.card}>\n {href ? (\n <Link to={href} className={styles.action} aria-label={ariaLabel}>\n {content}\n </Link>\n ) : (\n <div className={styles.action}>{content}</div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ApiCard.esm.js","sources":["../../src/components/ApiCard.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { Link } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\nimport { entityRouteRef } from '@backstage/plugin-catalog-react';\nimport { Text } from '@backstage/ui';\nimport {\n RiBuildingLine,\n RiFileTextLine,\n RiGlobalLine,\n RiLockLine,\n RiTeamLine,\n type RemixiconComponentType,\n} from '@remixicon/react';\nimport type {\n ApiCardViewModel,\n ApiVisibility,\n LifecycleBadgeKind,\n} from './apiCardViewModel';\nimport styles from './ApiCard.module.css';\n\nexport interface ApiCardProps {\n vm: ApiCardViewModel;\n /** When provided, clicking the card opens the details drawer (via this callback)\n * instead of navigating to the catalog entity page. */\n onSelect?: (vm: ApiCardViewModel) => void;\n}\n\nconst BADGE_KIND_CLASS: Record<LifecycleBadgeKind, string> = {\n ok: styles.badgeOk,\n warning: styles.badgeWarning,\n error: styles.badgeError,\n};\n\nexport const VISIBILITY_ICON: Record<ApiVisibility, RemixiconComponentType> = {\n private: RiLockLine,\n public: RiGlobalLine,\n};\n\n/** Solid colored status pill (D-04). Color conveys state; the text label is the\n * accessible source of truth so it never relies on color alone. */\nexport function LifecycleBadge({\n kind,\n label,\n}: {\n kind: LifecycleBadgeKind;\n label: string;\n}) {\n return (\n <span className={`${styles.badge} ${BADGE_KIND_CLASS[kind]}`}>{label}</span>\n );\n}\n\n/**\n * A single Apigee API tile. The whole card is one anchor-based control whose href is\n * resolved via `entityRouteRef` (never hardcoded — TR04); it degrades to a non-link\n * button when the route ref is unbound (dev harness). Entity text renders as React\n * children only — never via raw HTML injection (T-05-03). (UI-02, UI-03)\n */\nexport function ApiCard({ vm, onSelect }: ApiCardProps) {\n const entityRoute = useRouteRef(entityRouteRef);\n const { kind, namespace, name } = vm.targetRef;\n const href = entityRoute\n ? entityRoute({ kind: kind.toLowerCase(), namespace, name })\n : undefined;\n const ariaLabel = `Open ${vm.name} API details`;\n\n const content = (\n <>\n <div className={styles.header}>\n <div className={styles.titleRow}>\n <Text\n variant=\"body-large\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.name}\n >\n {vm.name}\n </Text>\n </div>\n <div className={styles.badges}>\n <LifecycleBadge kind={vm.badge.kind} label={vm.badge.label} />\n {vm.version ? (\n <span className={`${styles.badge} ${styles.badgeVersion}`}>\n {vm.version}\n </span>\n ) : null}\n </div>\n {vm.description ? (\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n as=\"p\"\n className={styles.description}\n >\n {vm.description}\n </Text>\n ) : null}\n </div>\n\n <div className={styles.body}>\n <div className={styles.leftCol}>\n <div className={styles.metaItem}>\n <RiTeamLine aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {vm.ownerLabel}\n </Text>\n </div>\n {vm.department ? (\n <div className={styles.metaItem}>\n <RiBuildingLine aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {vm.department}\n </Text>\n </div>\n ) : null}\n {vm.visibilities.map(visibility => {\n const VisibilityIcon = VISIBILITY_ICON[visibility];\n return (\n <div key={visibility} className={styles.metaItem}>\n <VisibilityIcon aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {visibility}\n </Text>\n </div>\n );\n })}\n {vm.hasSpec ? (\n <div className={styles.metaItem}>\n <RiFileTextLine aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n OpenAPI spec\n </Text>\n </div>\n ) : null}\n </div>\n\n <div className={styles.rightCol}>\n {/* Quality score + information coverage list — to be implemented later */}\n </div>\n </div>\n </>\n );\n\n if (onSelect) {\n return (\n <button\n type=\"button\"\n className={styles.card}\n aria-label={ariaLabel}\n onClick={() => onSelect(vm)}\n >\n {content}\n </button>\n );\n }\n\n return href ? (\n <Link to={href} className={styles.card} aria-label={ariaLabel}>\n {content}\n </Link>\n ) : (\n <div className={styles.card}>{content}</div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
2
2
 
3
- var css_248z = ".ApiCard_card__611f3d2e93 {\n height: 100%;\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-neutral-1);\n transition: box-shadow 200ms ease;\n}\n\n.ApiCard_card__611f3d2e93:hover {\n box-shadow: var(--bui-shadow);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ApiCard_card__611f3d2e93 {\n transition: none;\n }\n}\n\n.ApiCard_action__611f3d2e93 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n height: 100%;\n min-height: 172px;\n padding: var(--bui-space-4);\n color: inherit;\n text-decoration: none;\n border-radius: var(--bui-radius-3);\n}\n\n.ApiCard_action__611f3d2e93:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n}\n\n.ApiCard_header__611f3d2e93 {\n display: flex;\n align-items: flex-start;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_categoryIcon__611f3d2e93 {\n margin-top: 2px;\n color: var(--bui-fg-secondary);\n}\n\n.ApiCard_name__611f3d2e93 {\n flex: 1;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n word-break: break-word;\n}\n\n.ApiCard_meta__611f3d2e93 {\n display: flex;\n align-items: center;\n gap: var(--bui-space-1);\n color: var(--bui-fg-secondary);\n}\n\n.ApiCard_version__611f3d2e93 {\n margin-left: auto;\n}\n\n.ApiCard_description__611f3d2e93 {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.ApiCard_footer__611f3d2e93 {\n margin-top: auto;\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_specIcon__611f3d2e93 {\n display: inline-flex;\n color: var(--bui-fg-secondary);\n}\n";
4
- var styles = {"card":"ApiCard_card__611f3d2e93","action":"ApiCard_action__611f3d2e93","header":"ApiCard_header__611f3d2e93","categoryIcon":"ApiCard_categoryIcon__611f3d2e93","name":"ApiCard_name__611f3d2e93","meta":"ApiCard_meta__611f3d2e93","version":"ApiCard_version__611f3d2e93","description":"ApiCard_description__611f3d2e93","footer":"ApiCard_footer__611f3d2e93","specIcon":"ApiCard_specIcon__611f3d2e93"};
3
+ var css_248z = ".ApiCard_card__b286a50f58 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n height: 100%;\n min-height: 172px;\n padding: var(--bui-space-4);\n color: inherit;\n text-decoration: none;\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-neutral-1);\n transition: box-shadow 200ms ease;\n}\n\n/* The card IS a MUI Link; element+class beats MUI's class so its self-applied link colour/underline doesn't bleed onto the card content. */\na.ApiCard_card__b286a50f58,\na.ApiCard_card__b286a50f58:hover {\n color: inherit;\n text-decoration: none;\n}\n\n/* The card can also be a <button> (opens the details drawer). Reset UA button\n styling so it renders identically to the anchor/div variants. */\nbutton.ApiCard_card__b286a50f58 {\n width: 100%;\n font: inherit;\n text-align: left;\n cursor: pointer;\n appearance: none;\n}\n\n.ApiCard_card__b286a50f58:hover {\n box-shadow: var(--bui-shadow);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ApiCard_card__b286a50f58 {\n transition: none;\n }\n}\n\n.ApiCard_card__b286a50f58:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n}\n\n.ApiCard_header__b286a50f58 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_titleRow__b286a50f58 {\n display: flex;\n align-items: flex-start;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_name__b286a50f58 {\n flex: 1;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n word-break: break-word;\n}\n\n.ApiCard_badges__b286a50f58 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiCard_badge__b286a50f58 {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n padding: 2px var(--bui-space-2);\n border-radius: var(--bui-radius-full);\n font-size: var(--bui-font-size-1);\n font-weight: var(--bui-font-weight-bold);\n line-height: 1.5;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n white-space: nowrap;\n}\n\n.ApiCard_badgeOk__b286a50f58 {\n background-color: var(--bui-bg-success);\n color: var(--bui-fg-success-on-bg);\n}\n\n.ApiCard_badgeWarning__b286a50f58 {\n background-color: var(--bui-bg-warning);\n color: var(--bui-fg-warning-on-bg);\n}\n\n.ApiCard_badgeError__b286a50f58 {\n background-color: var(--bui-bg-danger);\n color: var(--bui-fg-danger-on-bg);\n}\n\n.ApiCard_badgeVersion__b286a50f58 {\n background-color: var(--bui-bg-info);\n color: var(--bui-fg-info-on-bg);\n text-transform: none;\n}\n\n.ApiCard_body__b286a50f58 {\n display: flex;\n align-items: flex-start;\n gap: var(--bui-space-4);\n}\n\n.ApiCard_leftCol__b286a50f58,\n.ApiCard_rightCol__b286a50f58 {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-1);\n}\n\n.ApiCard_leftCol__b286a50f58 {\n color: var(--bui-fg-secondary);\n}\n\n.ApiCard_metaItem__b286a50f58 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiCard_description__b286a50f58 {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n";
4
+ var styles = {"card":"ApiCard_card__b286a50f58","header":"ApiCard_header__b286a50f58","titleRow":"ApiCard_titleRow__b286a50f58","name":"ApiCard_name__b286a50f58","badges":"ApiCard_badges__b286a50f58","badge":"ApiCard_badge__b286a50f58","badgeOk":"ApiCard_badgeOk__b286a50f58","badgeWarning":"ApiCard_badgeWarning__b286a50f58","badgeError":"ApiCard_badgeError__b286a50f58","badgeVersion":"ApiCard_badgeVersion__b286a50f58","body":"ApiCard_body__b286a50f58","leftCol":"ApiCard_leftCol__b286a50f58","rightCol":"ApiCard_rightCol__b286a50f58","metaItem":"ApiCard_metaItem__b286a50f58","description":"ApiCard_description__b286a50f58"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { styles as default };
@@ -1,41 +1,13 @@
1
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
1
+ import { jsx } from 'react/jsx-runtime';
2
2
  import { ItemCardGrid } from '@backstage/core-components';
3
- import { Text } from '@backstage/ui';
4
3
  import { ApiCard } from './ApiCard.esm.js';
5
- import { resolveCategoryIcon } from './categoryIcons.esm.js';
6
- import { groupByCategory } from './grouping.esm.js';
7
- import styles from './ApiCardGrid.module.css.esm.js';
4
+ import { ApiTable } from './ApiTable.esm.js';
8
5
 
9
- function ApiCardGrid({ vms, viewMode, overrides }) {
10
- if (viewMode === "flat") {
11
- return /* @__PURE__ */ jsx(ItemCardGrid, { children: vms.map((vm) => /* @__PURE__ */ jsx(
12
- ApiCard,
13
- {
14
- vm,
15
- icon: resolveCategoryIcon(vm.category, overrides)
16
- },
17
- vm.targetRef.name
18
- )) });
6
+ function ApiCardGrid({ vms, viewMode, onSelect }) {
7
+ if (viewMode === "list") {
8
+ return /* @__PURE__ */ jsx(ApiTable, { vms, onSelect });
19
9
  }
20
- const groups = groupByCategory(vms);
21
- return /* @__PURE__ */ jsx(Fragment, { children: groups.map((group) => {
22
- const SectionIcon = resolveCategoryIcon(group.key, overrides);
23
- return /* @__PURE__ */ jsxs("section", { className: styles.section, children: [
24
- /* @__PURE__ */ jsxs("div", { className: styles.sectionHeader, children: [
25
- /* @__PURE__ */ jsx(SectionIcon, { "aria-hidden": true, className: styles.sectionIcon }),
26
- /* @__PURE__ */ jsx(Text, { variant: "title-small", as: "h2", children: group.label }),
27
- /* @__PURE__ */ jsx("span", { className: styles.count, children: group.items.length })
28
- ] }),
29
- /* @__PURE__ */ jsx(ItemCardGrid, { children: group.items.map((vm) => /* @__PURE__ */ jsx(
30
- ApiCard,
31
- {
32
- vm,
33
- icon: resolveCategoryIcon(vm.category, overrides)
34
- },
35
- vm.targetRef.name
36
- )) })
37
- ] }, group.key);
38
- }) });
10
+ return /* @__PURE__ */ jsx(ItemCardGrid, { children: vms.map((vm) => /* @__PURE__ */ jsx(ApiCard, { vm, onSelect }, vm.targetRef.name)) });
39
11
  }
40
12
 
41
13
  export { ApiCardGrid };
@@ -1 +1 @@
1
- {"version":3,"file":"ApiCardGrid.esm.js","sources":["../../src/components/ApiCardGrid.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { ItemCardGrid } from '@backstage/core-components';\nimport { Text } from '@backstage/ui';\nimport { ApiCard } from './ApiCard';\nimport type { ApiCardViewModel } from './apiCardViewModel';\nimport { resolveCategoryIcon } from './categoryIcons';\nimport { groupByCategory } from './grouping';\nimport styles from './ApiCardGrid.module.css';\n\nexport type ViewMode = 'flat' | 'grouped';\n\nexport interface ApiCardGridProps {\n vms: ApiCardViewModel[];\n viewMode: ViewMode;\n /** Raw `apigee.catalog.categoryIcons` override map (slug -> IconKey string). */\n overrides?: Record<string, string>;\n}\n\n/**\n * Presentational grid of `ApiCard` tiles. In `flat` mode a single responsive grid; in\n * `grouped` mode one section per category (icon + label + count chip) with the \"Other\"\n * bucket last (ordering enforced by `groupByCategory`). No data fetching. (UI-01, UI-11)\n */\nexport function ApiCardGrid({ vms, viewMode, overrides }: ApiCardGridProps) {\n if (viewMode === 'flat') {\n return (\n <ItemCardGrid>\n {vms.map(vm => (\n <ApiCard\n key={vm.targetRef.name}\n vm={vm}\n icon={resolveCategoryIcon(vm.category, overrides)}\n />\n ))}\n </ItemCardGrid>\n );\n }\n\n const groups = groupByCategory(vms);\n return (\n <>\n {groups.map(group => {\n const SectionIcon = resolveCategoryIcon(group.key, overrides);\n return (\n <section key={group.key} className={styles.section}>\n <div className={styles.sectionHeader}>\n <SectionIcon aria-hidden className={styles.sectionIcon} />\n <Text variant=\"title-small\" as=\"h2\">\n {group.label}\n </Text>\n <span className={styles.count}>{group.items.length}</span>\n </div>\n <ItemCardGrid>\n {group.items.map(vm => (\n <ApiCard\n key={vm.targetRef.name}\n vm={vm}\n icon={resolveCategoryIcon(vm.category, overrides)}\n />\n ))}\n </ItemCardGrid>\n </section>\n );\n })}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAuBO,SAAS,WAAA,CAAY,EAAE,GAAA,EAAK,QAAA,EAAU,WAAU,EAAqB;AAC1E,EAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,IAAA,uBACE,GAAA,CAAC,YAAA,EAAA,EACE,QAAA,EAAA,GAAA,CAAI,GAAA,CAAI,CAAA,EAAA,qBACP,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QAEC,EAAA;AAAA,QACA,IAAA,EAAM,mBAAA,CAAoB,EAAA,CAAG,QAAA,EAAU,SAAS;AAAA,OAAA;AAAA,MAF3C,GAAG,SAAA,CAAU;AAAA,KAIrB,CAAA,EACH,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,MAAA,GAAS,gBAAgB,GAAG,CAAA;AAClC,EAAA,uBACE,GAAA,CAAA,QAAA,EAAA,EACG,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AACnB,IAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,KAAA,CAAM,GAAA,EAAK,SAAS,CAAA;AAC5D,IAAA,uBACE,IAAA,CAAC,SAAA,EAAA,EAAwB,SAAA,EAAW,MAAA,CAAO,OAAA,EACzC,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,aAAA,EACrB,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,aAAA,EAAW,IAAA,EAAC,SAAA,EAAW,OAAO,WAAA,EAAa,CAAA;AAAA,4BACvD,IAAA,EAAA,EAAK,OAAA,EAAQ,eAAc,EAAA,EAAG,IAAA,EAC5B,gBAAM,KAAA,EACT,CAAA;AAAA,4BACC,MAAA,EAAA,EAAK,SAAA,EAAW,OAAO,KAAA,EAAQ,QAAA,EAAA,KAAA,CAAM,MAAM,MAAA,EAAO;AAAA,OAAA,EACrD,CAAA;AAAA,sBACA,GAAA,CAAC,YAAA,EAAA,EACE,QAAA,EAAA,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA,EAAA,qBACf,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UAEC,EAAA;AAAA,UACA,IAAA,EAAM,mBAAA,CAAoB,EAAA,CAAG,QAAA,EAAU,SAAS;AAAA,SAAA;AAAA,QAF3C,GAAG,SAAA,CAAU;AAAA,OAIrB,CAAA,EACH;AAAA,KAAA,EAAA,EAhBY,MAAM,GAiBpB,CAAA;AAAA,EAEJ,CAAC,CAAA,EACH,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"ApiCardGrid.esm.js","sources":["../../src/components/ApiCardGrid.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { ItemCardGrid } from '@backstage/core-components';\nimport { ApiCard } from './ApiCard';\nimport { ApiTable } from './ApiTable';\nimport type { ApiCardViewModel } from './apiCardViewModel';\n\nexport type ViewMode = 'flat' | 'list';\n\nexport interface ApiCardGridProps {\n vms: ApiCardViewModel[];\n viewMode: ViewMode;\n /** Opens the details drawer for a clicked card/row (both views). */\n onSelect?: (vm: ApiCardViewModel) => void;\n}\n\n/**\n * Presentational grid of `ApiCard` tiles. In `flat` mode a single responsive grid; in\n * `list` mode a table. No data fetching. (UI-01, UI-11)\n */\nexport function ApiCardGrid({ vms, viewMode, onSelect }: ApiCardGridProps) {\n if (viewMode === 'list') {\n return <ApiTable vms={vms} onSelect={onSelect} />;\n }\n\n return (\n <ItemCardGrid>\n {vms.map(vm => (\n <ApiCard key={vm.targetRef.name} vm={vm} onSelect={onSelect} />\n ))}\n </ItemCardGrid>\n );\n}\n"],"names":[],"mappings":";;;;;AAmBO,SAAS,WAAA,CAAY,EAAE,GAAA,EAAK,QAAA,EAAU,UAAS,EAAqB;AACzE,EAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,IAAA,uBAAO,GAAA,CAAC,QAAA,EAAA,EAAS,GAAA,EAAU,QAAA,EAAoB,CAAA;AAAA,EACjD;AAEA,EAAA,uBACE,GAAA,CAAC,YAAA,EAAA,EACE,QAAA,EAAA,GAAA,CAAI,GAAA,CAAI,CAAA,EAAA,qBACP,GAAA,CAAC,OAAA,EAAA,EAAgC,EAAA,EAAQ,QAAA,EAAA,EAA3B,EAAA,CAAG,SAAA,CAAU,IAAkC,CAC9D,CAAA,EACH,CAAA;AAEJ;;;;"}
@@ -0,0 +1,335 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { Link } from '@backstage/core-components';
3
+ import { useApi } from '@backstage/core-plugin-api';
4
+ import { useRouteRef } from '@backstage/frontend-plugin-api';
5
+ import { parseEntityRef } from '@backstage/catalog-model';
6
+ import { catalogApiRef, entityRouteRef, EntityRefLink } from '@backstage/plugin-catalog-react';
7
+ import { Text, ButtonIcon, Button, Tabs, TabList, Tab, TabPanel } from '@backstage/ui';
8
+ import { RiCloseLine, RiExternalLinkLine, RiDownloadLine, RiFileCopyLine } from '@remixicon/react';
9
+ import { useState, useEffect, useMemo, Fragment as Fragment$1 } from 'react';
10
+ import { useNavigate } from 'react-router-dom';
11
+ import { LifecycleBadge } from './ApiCard.esm.js';
12
+ import { toApiDetailViewModel, specFileName } from './apiDetailViewModel.esm.js';
13
+ import styles from './ApiDetailsDrawer.module.css.esm.js';
14
+
15
+ function MetaRow({
16
+ label,
17
+ value,
18
+ children
19
+ }) {
20
+ return /* @__PURE__ */ jsxs("div", { className: styles.metaRow, children: [
21
+ /* @__PURE__ */ jsx("dt", { className: styles.metaLabel, children: label }),
22
+ /* @__PURE__ */ jsx("dd", { className: styles.metaValue, children: children ?? value })
23
+ ] });
24
+ }
25
+ function OwnerValue({ vm }) {
26
+ if (vm.ownerRef) {
27
+ try {
28
+ parseEntityRef(vm.ownerRef, {
29
+ defaultKind: "group",
30
+ defaultNamespace: "default"
31
+ });
32
+ return /* @__PURE__ */ jsx(EntityRefLink, { entityRef: vm.ownerRef, defaultKind: "group" });
33
+ } catch {
34
+ }
35
+ }
36
+ return /* @__PURE__ */ jsx(Fragment, { children: vm.ownerLabel });
37
+ }
38
+ function SpecSection({
39
+ definition,
40
+ fileBaseName,
41
+ definitionHref
42
+ }) {
43
+ const navigate = useNavigate();
44
+ const [copied, setCopied] = useState(false);
45
+ if (!definition.trim()) {
46
+ return /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "p", children: "No specification available for preview." });
47
+ }
48
+ const handleCopy = async () => {
49
+ await navigator.clipboard.writeText(definition);
50
+ setCopied(true);
51
+ setTimeout(() => setCopied(false), 2e3);
52
+ };
53
+ const handleDownload = () => {
54
+ const blob = new Blob([definition], { type: "text/plain;charset=utf-8" });
55
+ const url = URL.createObjectURL(blob);
56
+ try {
57
+ const anchor = document.createElement("a");
58
+ anchor.href = url;
59
+ anchor.download = specFileName(fileBaseName, definition);
60
+ anchor.click();
61
+ } finally {
62
+ URL.revokeObjectURL(url);
63
+ }
64
+ };
65
+ return /* @__PURE__ */ jsxs("div", { className: styles.spec, children: [
66
+ /* @__PURE__ */ jsxs("div", { className: styles.specActions, children: [
67
+ /* @__PURE__ */ jsx(
68
+ Button,
69
+ {
70
+ size: "small",
71
+ variant: "secondary",
72
+ iconStart: /* @__PURE__ */ jsx(RiDownloadLine, {}),
73
+ onPress: handleDownload,
74
+ children: "Download"
75
+ }
76
+ ),
77
+ /* @__PURE__ */ jsx(
78
+ Button,
79
+ {
80
+ size: "small",
81
+ variant: "secondary",
82
+ iconStart: /* @__PURE__ */ jsx(RiFileCopyLine, {}),
83
+ onPress: handleCopy,
84
+ children: copied ? "Copied!" : "Copy"
85
+ }
86
+ )
87
+ ] }),
88
+ /* @__PURE__ */ jsxs(
89
+ Tabs,
90
+ {
91
+ selectedKey: "raw",
92
+ onSelectionChange: (key) => {
93
+ if (key === "openapi" && definitionHref) {
94
+ navigate(definitionHref);
95
+ }
96
+ },
97
+ children: [
98
+ /* @__PURE__ */ jsxs(TabList, { "aria-label": "Specification format", className: styles.tabList, children: [
99
+ /* @__PURE__ */ jsx(Tab, { id: "openapi", isDisabled: !definitionHref, children: /* @__PURE__ */ jsxs("span", { className: styles.tabLabel, children: [
100
+ "OpenAPI ",
101
+ /* @__PURE__ */ jsx(RiExternalLinkLine, { "aria-hidden": true, size: 12 })
102
+ ] }) }),
103
+ /* @__PURE__ */ jsx(Tab, { id: "raw", children: "Raw" })
104
+ ] }),
105
+ /* @__PURE__ */ jsx(TabPanel, { id: "raw", children: /* @__PURE__ */ jsx("pre", { className: styles.code, children: /* @__PURE__ */ jsx("code", { children: definition }) }) })
106
+ ]
107
+ }
108
+ )
109
+ ] });
110
+ }
111
+ function ApiDetailsDrawerContent({
112
+ entity,
113
+ orgLabels,
114
+ onClose
115
+ }) {
116
+ const catalogApi = useApi(catalogApiRef);
117
+ const entityRoute = useRouteRef(entityRouteRef);
118
+ const [component, setComponent] = useState();
119
+ const namespace = entity.metadata.namespace ?? "default";
120
+ const name = entity.metadata.name;
121
+ useEffect(() => {
122
+ let active = true;
123
+ catalogApi.getEntityByRef({ kind: "Component", namespace, name }).then((found) => {
124
+ if (active) {
125
+ setComponent(found ?? void 0);
126
+ }
127
+ }).catch(() => {
128
+ if (active) {
129
+ setComponent(void 0);
130
+ }
131
+ });
132
+ return () => {
133
+ active = false;
134
+ };
135
+ }, [catalogApi, namespace, name]);
136
+ const vm = useMemo(
137
+ () => toApiDetailViewModel(entity, component, orgLabels),
138
+ [entity, component, orgLabels]
139
+ );
140
+ const catalogHref = entityRoute ? entityRoute({
141
+ kind: vm.targetRef.kind.toLowerCase(),
142
+ namespace: vm.targetRef.namespace,
143
+ name: vm.targetRef.name
144
+ }) : void 0;
145
+ const [open, setOpen] = useState(false);
146
+ useEffect(() => {
147
+ const id = requestAnimationFrame(() => setOpen(true));
148
+ return () => cancelAnimationFrame(id);
149
+ }, []);
150
+ useEffect(() => {
151
+ const onKey = (event) => {
152
+ if (event.key === "Escape") {
153
+ onClose();
154
+ }
155
+ };
156
+ document.addEventListener("keydown", onKey);
157
+ return () => document.removeEventListener("keydown", onKey);
158
+ }, [onClose]);
159
+ const showDeployments = vm.runtimeOrgs.length > 0;
160
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
161
+ /* @__PURE__ */ jsx(
162
+ "div",
163
+ {
164
+ className: styles.overlay,
165
+ "data-open": open,
166
+ onClick: onClose,
167
+ role: "presentation",
168
+ "aria-hidden": !open
169
+ }
170
+ ),
171
+ /* @__PURE__ */ jsxs(
172
+ "aside",
173
+ {
174
+ className: styles.drawer,
175
+ "data-open": open,
176
+ role: "dialog",
177
+ "aria-modal": "true",
178
+ "aria-label": `${vm.name} API details`,
179
+ children: [
180
+ /* @__PURE__ */ jsxs("header", { className: styles.header, children: [
181
+ /* @__PURE__ */ jsxs("div", { className: styles.headerText, children: [
182
+ /* @__PURE__ */ jsx(
183
+ Text,
184
+ {
185
+ variant: "title-small",
186
+ weight: "bold",
187
+ as: "h2",
188
+ className: styles.title,
189
+ children: vm.name
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsxs("div", { className: styles.badges, children: [
193
+ vm.version ? /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${styles.badgeVersion}`, children: vm.version }) : null,
194
+ /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label })
195
+ ] })
196
+ ] }),
197
+ /* @__PURE__ */ jsx(
198
+ ButtonIcon,
199
+ {
200
+ icon: /* @__PURE__ */ jsx(RiCloseLine, {}),
201
+ variant: "tertiary",
202
+ onPress: onClose,
203
+ "aria-label": "Close"
204
+ }
205
+ )
206
+ ] }),
207
+ /* @__PURE__ */ jsxs("div", { className: styles.body, children: [
208
+ /* @__PURE__ */ jsxs("section", { className: styles.card, "aria-labelledby": "apigee-overview-title", children: [
209
+ /* @__PURE__ */ jsx(
210
+ Text,
211
+ {
212
+ id: "apigee-overview-title",
213
+ variant: "title-x-small",
214
+ weight: "bold",
215
+ as: "h3",
216
+ className: styles.cardTitle,
217
+ children: "Overview"
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsxs("dl", { className: styles.metaList, children: [
221
+ /* @__PURE__ */ jsx(MetaRow, { label: "Title", value: vm.name }),
222
+ vm.description ? /* @__PURE__ */ jsx(MetaRow, { label: "Description", value: vm.description }) : null,
223
+ /* @__PURE__ */ jsx(MetaRow, { label: "Kind", value: vm.kind }),
224
+ vm.type ? /* @__PURE__ */ jsx(MetaRow, { label: "Type", value: vm.type }) : null,
225
+ vm.version ? /* @__PURE__ */ jsx(MetaRow, { label: "Version", value: vm.version }) : null,
226
+ /* @__PURE__ */ jsx(MetaRow, { label: "Lifecycle", value: vm.badge.label }),
227
+ /* @__PURE__ */ jsx(MetaRow, { label: "Owner", children: /* @__PURE__ */ jsx(OwnerValue, { vm }) }),
228
+ vm.department ? /* @__PURE__ */ jsx(MetaRow, { label: "Department", value: vm.department }) : null,
229
+ vm.tags.length > 0 ? /* @__PURE__ */ jsx(MetaRow, { label: "Tags", children: /* @__PURE__ */ jsx("div", { className: styles.tags, children: vm.tags.map((tag) => /* @__PURE__ */ jsx("span", { className: styles.tag, children: tag }, tag)) }) }) : null
230
+ ] }),
231
+ showDeployments ? /* @__PURE__ */ jsxs("div", { className: styles.deployments, children: [
232
+ /* @__PURE__ */ jsx(
233
+ Text,
234
+ {
235
+ variant: "body-small",
236
+ weight: "bold",
237
+ as: "h4",
238
+ className: styles.deploymentsTitle,
239
+ children: "Deployments"
240
+ }
241
+ ),
242
+ /* @__PURE__ */ jsxs("table", { className: styles.deploymentsTable, children: [
243
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
244
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Org" }),
245
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Environment" }),
246
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Revision" })
247
+ ] }) }),
248
+ /* @__PURE__ */ jsx("tbody", { children: vm.runtimeOrgs.map((org) => /* @__PURE__ */ jsx(Fragment$1, { children: org.environments.length > 0 ? org.environments.map((env, index) => /* @__PURE__ */ jsxs("tr", { children: [
249
+ index === 0 ? /* @__PURE__ */ jsx(
250
+ "th",
251
+ {
252
+ scope: "rowgroup",
253
+ rowSpan: org.environments.length,
254
+ className: styles.orgCell,
255
+ children: org.label
256
+ }
257
+ ) : null,
258
+ /* @__PURE__ */ jsx("td", { children: env.name }),
259
+ /* @__PURE__ */ jsx("td", { children: env.revision })
260
+ ] }, env.name)) : /* @__PURE__ */ jsxs("tr", { children: [
261
+ /* @__PURE__ */ jsx("th", { scope: "row", className: styles.orgCell, children: org.label }),
262
+ /* @__PURE__ */ jsx("td", { colSpan: 2, className: styles.noDeployments, children: "No deployments" })
263
+ ] }) }, org.value)) })
264
+ ] })
265
+ ] }) : null
266
+ ] }),
267
+ /* @__PURE__ */ jsxs("section", { className: styles.card, "aria-labelledby": "apigee-spec-title", children: [
268
+ /* @__PURE__ */ jsx(
269
+ Text,
270
+ {
271
+ id: "apigee-spec-title",
272
+ variant: "title-x-small",
273
+ weight: "bold",
274
+ as: "h3",
275
+ className: styles.cardTitle,
276
+ children: "Specification"
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ SpecSection,
281
+ {
282
+ definition: vm.definition,
283
+ fileBaseName: vm.targetRef.name,
284
+ definitionHref: catalogHref ? `${catalogHref}/definition` : void 0
285
+ }
286
+ )
287
+ ] }),
288
+ catalogHref || vm.sourceRepo ? /* @__PURE__ */ jsxs("section", { className: styles.card, "aria-labelledby": "apigee-links-title", children: [
289
+ /* @__PURE__ */ jsx(
290
+ Text,
291
+ {
292
+ id: "apigee-links-title",
293
+ variant: "title-x-small",
294
+ weight: "bold",
295
+ as: "h3",
296
+ className: styles.cardTitle,
297
+ children: "Links"
298
+ }
299
+ ),
300
+ catalogHref ? /* @__PURE__ */ jsxs(Link, { to: catalogHref, className: styles.catalogLink, children: [
301
+ /* @__PURE__ */ jsx(RiExternalLinkLine, { "aria-hidden": true, size: 14 }),
302
+ " View in catalog"
303
+ ] }) : null,
304
+ vm.sourceRepo ? /* @__PURE__ */ jsxs(Link, { to: vm.sourceRepo.url, className: styles.sourceRepo, children: [
305
+ /* @__PURE__ */ jsx(RiExternalLinkLine, { "aria-hidden": true, size: 14 }),
306
+ " ",
307
+ vm.sourceRepo.title
308
+ ] }) : null
309
+ ] }) : null
310
+ ] })
311
+ ]
312
+ }
313
+ )
314
+ ] });
315
+ }
316
+ function ApiDetailsDrawer({
317
+ entity,
318
+ orgLabels,
319
+ onClose
320
+ }) {
321
+ if (!entity) {
322
+ return null;
323
+ }
324
+ return /* @__PURE__ */ jsx(
325
+ ApiDetailsDrawerContent,
326
+ {
327
+ entity,
328
+ orgLabels,
329
+ onClose
330
+ }
331
+ );
332
+ }
333
+
334
+ export { ApiDetailsDrawer };
335
+ //# sourceMappingURL=ApiDetailsDrawer.esm.js.map