@nospt/backstage-plugin-apigee 1.1.1

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 (74) hide show
  1. package/README.md +98 -0
  2. package/config.d.ts +30 -0
  3. package/dist/components/ApiCard.esm.js +105 -0
  4. package/dist/components/ApiCard.esm.js.map +1 -0
  5. package/dist/components/ApiCard.module.css.esm.js +8 -0
  6. package/dist/components/ApiCard.module.css.esm.js.map +1 -0
  7. package/dist/components/ApiCardGrid.esm.js +14 -0
  8. package/dist/components/ApiCardGrid.esm.js.map +1 -0
  9. package/dist/components/ApiDetailsDrawer.esm.js +709 -0
  10. package/dist/components/ApiDetailsDrawer.esm.js.map +1 -0
  11. package/dist/components/ApiDetailsDrawer.module.css.esm.js +8 -0
  12. package/dist/components/ApiDetailsDrawer.module.css.esm.js.map +1 -0
  13. package/dist/components/ApiTable.esm.js +184 -0
  14. package/dist/components/ApiTable.esm.js.map +1 -0
  15. package/dist/components/ApiTable.module.css.esm.js +8 -0
  16. package/dist/components/ApiTable.module.css.esm.js.map +1 -0
  17. package/dist/components/ApigeeApiCatalogPage.esm.js +245 -0
  18. package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -0
  19. package/dist/components/ApigeeHomePage.esm.js +190 -0
  20. package/dist/components/ApigeeHomePage.esm.js.map +1 -0
  21. package/dist/components/ApigeeHomePage.module.css.esm.js +8 -0
  22. package/dist/components/ApigeeHomePage.module.css.esm.js.map +1 -0
  23. package/dist/components/ApigeeNavContent.esm.js +28 -0
  24. package/dist/components/ApigeeNavContent.esm.js.map +1 -0
  25. package/dist/components/CountCards.esm.js +120 -0
  26. package/dist/components/CountCards.esm.js.map +1 -0
  27. package/dist/components/CountCards.module.css.esm.js +8 -0
  28. package/dist/components/CountCards.module.css.esm.js.map +1 -0
  29. package/dist/components/FilterToolbar.esm.js +131 -0
  30. package/dist/components/FilterToolbar.esm.js.map +1 -0
  31. package/dist/components/FilterToolbar.module.css.esm.js +8 -0
  32. package/dist/components/FilterToolbar.module.css.esm.js.map +1 -0
  33. package/dist/components/SearchBar.esm.js +20 -0
  34. package/dist/components/SearchBar.esm.js.map +1 -0
  35. package/dist/components/apiCardViewModel.esm.js +57 -0
  36. package/dist/components/apiCardViewModel.esm.js.map +1 -0
  37. package/dist/components/apiDetailViewModel.esm.js +98 -0
  38. package/dist/components/apiDetailViewModel.esm.js.map +1 -0
  39. package/dist/components/apiOperations.esm.js +196 -0
  40. package/dist/components/apiOperations.esm.js.map +1 -0
  41. package/dist/components/apigeeFacetFilters.esm.js +62 -0
  42. package/dist/components/apigeeFacetFilters.esm.js.map +1 -0
  43. package/dist/components/apigeeOrgFilter.esm.js +11 -0
  44. package/dist/components/apigeeOrgFilter.esm.js.map +1 -0
  45. package/dist/components/apigeeSearchParams.esm.js +55 -0
  46. package/dist/components/apigeeSearchParams.esm.js.map +1 -0
  47. package/dist/components/brandIcons.esm.js +36 -0
  48. package/dist/components/brandIcons.esm.js.map +1 -0
  49. package/dist/components/codeHighlight.esm.js +71 -0
  50. package/dist/components/codeHighlight.esm.js.map +1 -0
  51. package/dist/components/codeSnippet.esm.js +128 -0
  52. package/dist/components/codeSnippet.esm.js.map +1 -0
  53. package/dist/components/facetModel.esm.js +86 -0
  54. package/dist/components/facetModel.esm.js.map +1 -0
  55. package/dist/components/grouping.esm.js +6 -0
  56. package/dist/components/grouping.esm.js.map +1 -0
  57. package/dist/components/states.esm.js +65 -0
  58. package/dist/components/states.esm.js.map +1 -0
  59. package/dist/components/useApigeeFilters.esm.js +191 -0
  60. package/dist/components/useApigeeFilters.esm.js.map +1 -0
  61. package/dist/components/useDebouncedValue.esm.js +13 -0
  62. package/dist/components/useDebouncedValue.esm.js.map +1 -0
  63. package/dist/constants.esm.js +9 -0
  64. package/dist/constants.esm.js.map +1 -0
  65. package/dist/index.d.ts +203 -0
  66. package/dist/index.esm.js +2 -0
  67. package/dist/index.esm.js.map +1 -0
  68. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
  69. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
  70. package/dist/plugin.esm.js +59 -0
  71. package/dist/plugin.esm.js.map +1 -0
  72. package/dist/routes.esm.js +8 -0
  73. package/dist/routes.esm.js.map +1 -0
  74. package/package.json +95 -0
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @nospt/backstage-plugin-apigee
2
+
3
+ The **frontend** package of the [Backstage Apigee plugin](../../README.md) — a dedicated **API Hub** experience for browsing the Apigee proxies and Apigee API Hub definitions that the [backend module](../apigee-backend/README.md) syncs into your catalog.
4
+
5
+ Built on the Backstage **New Frontend System**.
6
+
7
+ ## What you get
8
+
9
+ - **API Hub dashboard** (`/api-hub`) — an overview with live counts of your APIs by lifecycle (production / experimental / deprecated) and quick links into the catalog.
10
+ - **API catalog** (`/api-hub/catalog`) — a searchable, paginated grid of API cards with:
11
+ - facet filters for **lifecycle**, **org**, **owner**, and **department**;
12
+ - full-text search and a configurable page-size selector;
13
+ - a details drawer showing metadata and the embedded or linked OpenAPI/AsyncAPI spec.
14
+ - **Shareable views** — filters, search, and paging are all reflected in the URL (`?lifecycle=`, `?org=`, `?owner=`, `?department=`, `?q=`, `?page=`, `?size=`), so any view can be bookmarked or shared.
15
+
16
+ The catalog only shows entities produced by the Apigee backend (identified by their `cloud.google.com/*` annotations), so it lives happily alongside the rest of your Backstage catalog.
17
+
18
+ <!-- Replace these placeholders with real screenshots or GIFs (see ../../docs/images/README.md). -->
19
+
20
+ ![The API Hub dashboard: API counts by lifecycle with quick links into the catalog](../../docs/images/api-hub-dashboard.png)
21
+
22
+ ![The API catalog: a searchable, filterable grid of API cards](../../docs/images/api-catalog.png)
23
+
24
+ ## Routes
25
+
26
+ | Path | Tab | Description |
27
+ | ------------------ | ----------- | ------------------------------------------------- |
28
+ | `/api-hub` | Home | Landing dashboard with API stats and quick links. |
29
+ | `/api-hub/catalog` | API catalog | Filterable, searchable grid of API entities. |
30
+
31
+ ## Installation
32
+
33
+ Add the package to your app:
34
+
35
+ ```bash
36
+ yarn --cwd packages/app add @nospt/backstage-plugin-apigee
37
+ ```
38
+
39
+ Register the plugin in `packages/app/src/App.tsx`:
40
+
41
+ ```ts
42
+ import apigeePlugin from '@nospt/backstage-plugin-apigee';
43
+
44
+ export const app = createApp({
45
+ features: [
46
+ // ...
47
+ apigeePlugin,
48
+ ],
49
+ });
50
+ ```
51
+
52
+ ### Optional: grouped navigation
53
+
54
+ The package also exports `apigeeNavModule`, a New Frontend System nav module that renders an expandable **API Hub** group (with the Home and API catalog sub-pages) in the app sidebar. Add it to your app's features to enable it:
55
+
56
+ ```ts
57
+ import apigeePlugin, { apigeeNavModule } from '@nospt/backstage-plugin-apigee';
58
+
59
+ export const app = createApp({
60
+ features: [apigeePlugin, apigeeNavModule],
61
+ });
62
+ ```
63
+
64
+ If you manage the sidebar with your own nav module, skip `apigeeNavModule` and link to `/api-hub` directly.
65
+
66
+ ## Configuration
67
+
68
+ All frontend settings are optional and live under `apigee.catalog`:
69
+
70
+ ```yaml
71
+ apigee:
72
+ catalog:
73
+ # Friendly labels for the "Org" filter facet: raw org id -> display label.
74
+ orgLabels:
75
+ apigee-x-private-p-495866: Private
76
+ apigee-x-public-p-863498: Public
77
+ # Initial API cards per page. Users can change it via the selector
78
+ # (10/20/30/40/50). Values below 1 are floored to 1; defaults to 10.
79
+ pageSize: 24
80
+ ```
81
+
82
+ | Key | Type | Default | Description |
83
+ | -------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------ |
84
+ | `apigee.catalog.orgLabels` | map | — | Maps raw org ids to friendly labels shown in the **Org** facet. Unmapped ids fall back to a humanised label. |
85
+ | `apigee.catalog.pageSize` | number | `10` | Initial page size for the catalog grid. |
86
+
87
+ The entities themselves (and everything shown on the cards) come from the backend module — see its [configuration reference](../apigee-backend/README.md#configuration).
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ cd plugins/apigee
93
+ yarn start # run the isolated dev harness
94
+ yarn test # unit tests
95
+ yarn test:e2e # Playwright end-to-end tests
96
+ yarn lint
97
+ yarn build
98
+ ```
package/config.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ export interface Config {
3
+ apigee?: {
4
+ catalog?: {
5
+ /**
6
+ * Optional org-id -> display-label map for the Org facet.
7
+ * Keys are raw annotation values (for example `apigee-x-private-p-495866`);
8
+ * values are user-facing labels (for example `Private`).
9
+ * Unmapped org IDs fall back to the default humanised label.
10
+ *
11
+ * Deep visibility (the tag below) is required because this is a map with
12
+ * dynamic keys: plain frontend visibility exposes only the container, so the
13
+ * config-visibility filter strips the label values and the frontend receives
14
+ * an empty object. Deep visibility propagates to the values instead.
15
+ * Keep that tag as the ONLY at-sign token in this comment — the schema parser
16
+ * reads any at-sign visibility word in the prose as a real tag, and a stray
17
+ * one breaks compilation with an "invalid visibility value" error.
18
+ * @deepVisibility frontend
19
+ */
20
+ orgLabels?: { [orgId: string]: string };
21
+ /**
22
+ * Initial number of API cards shown per page in the Apigee catalog grid. Users can
23
+ * change it via the pagination selector (10/20/30/40/50). Values below 1 are floored
24
+ * to 1; defaults to 20 when unset.
25
+ * @visibility frontend
26
+ */
27
+ pageSize?: number;
28
+ };
29
+ };
30
+ }
@@ -0,0 +1,105 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { Link } from '@backstage/core-components';
3
+ import { useRouteRef } from '@backstage/frontend-plugin-api';
4
+ import { entityRouteRef } from '@backstage/plugin-catalog-react';
5
+ import { Text } from '@backstage/ui';
6
+ import { RiGlobalLine, RiLockLine, RiTeamLine, RiBuildingLine, RiFileTextLine } from '@remixicon/react';
7
+ import { Fragment as Fragment$1 } from 'react';
8
+ import styles from './ApiCard.module.css.esm.js';
9
+
10
+ const BADGE_KIND_CLASS = {
11
+ ok: styles.badgeOk,
12
+ warning: styles.badgeWarning,
13
+ error: styles.badgeError
14
+ };
15
+ const VISIBILITY_ICON = {
16
+ private: RiLockLine,
17
+ public: RiGlobalLine
18
+ };
19
+ function LifecycleBadge({
20
+ kind,
21
+ label
22
+ }) {
23
+ return /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${BADGE_KIND_CLASS[kind]}`, children: label });
24
+ }
25
+ function ApiCard({ vm, onSelect }) {
26
+ const entityRoute = useRouteRef(entityRouteRef);
27
+ const { kind, namespace, name } = vm.targetRef;
28
+ const href = entityRoute ? entityRoute({ kind: kind.toLowerCase(), namespace, name }) : void 0;
29
+ const ariaLabel = `Open ${vm.name} API details`;
30
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsxs("div", { className: styles.header, children: [
32
+ /* @__PURE__ */ jsx("div", { className: styles.titleRow, children: /* @__PURE__ */ jsx(
33
+ Text,
34
+ {
35
+ variant: "body-large",
36
+ weight: "bold",
37
+ as: "h3",
38
+ className: styles.name,
39
+ children: vm.name
40
+ }
41
+ ) }),
42
+ /* @__PURE__ */ jsxs("div", { className: styles.badges, children: [
43
+ /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label }),
44
+ vm.version ? /* @__PURE__ */ jsx("span", { className: `${styles.badge} ${styles.badgeVersion}`, children: vm.version }) : null
45
+ ] }),
46
+ vm.description ? /* @__PURE__ */ jsx(
47
+ Text,
48
+ {
49
+ variant: "body-small",
50
+ color: "secondary",
51
+ as: "p",
52
+ className: styles.description,
53
+ children: vm.description
54
+ }
55
+ ) : null
56
+ ] }),
57
+ /* @__PURE__ */ jsx("div", { className: styles.body, children: /* @__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
+ /* @__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 ?? "unknown" })
65
+ ] }),
66
+ vm.visibilities.length > 0 ? /* @__PURE__ */ jsx("div", { className: styles.metaItem, children: vm.visibilities.map((visibility, index) => {
67
+ const VisibilityIcon = VISIBILITY_ICON[visibility];
68
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
69
+ index > 0 ? /* @__PURE__ */ jsx(
70
+ Text,
71
+ {
72
+ variant: "body-small",
73
+ color: "secondary",
74
+ as: "span",
75
+ "aria-hidden": true,
76
+ children: "|"
77
+ }
78
+ ) : null,
79
+ /* @__PURE__ */ jsx(VisibilityIcon, { "aria-hidden": true, size: 16 }),
80
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: visibility })
81
+ ] }, visibility);
82
+ }) }) : null,
83
+ vm.hasSpec ? /* @__PURE__ */ jsxs("div", { className: styles.metaItem, children: [
84
+ /* @__PURE__ */ jsx(RiFileTextLine, { "aria-hidden": true, size: 16 }),
85
+ /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "span", children: "OpenAPI spec" })
86
+ ] }) : null
87
+ ] }) })
88
+ ] });
89
+ if (onSelect) {
90
+ return /* @__PURE__ */ jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ className: styles.card,
95
+ "aria-label": ariaLabel,
96
+ onClick: () => onSelect(vm),
97
+ children: content
98
+ }
99
+ );
100
+ }
101
+ return href ? /* @__PURE__ */ jsx(Link, { to: href, className: styles.card, "aria-label": ariaLabel, children: content }) : /* @__PURE__ */ jsx("div", { className: styles.card, children: content });
102
+ }
103
+
104
+ export { ApiCard, LifecycleBadge, VISIBILITY_ICON };
105
+ //# sourceMappingURL=ApiCard.esm.js.map
@@ -0,0 +1 @@
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 { Fragment } from '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 <div className={styles.metaItem}>\n <RiBuildingLine aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {vm.department ?? 'unknown'}\n </Text>\n </div>\n {vm.visibilities.length > 0 ? (\n <div className={styles.metaItem}>\n {vm.visibilities.map((visibility, index) => {\n const VisibilityIcon = VISIBILITY_ICON[visibility];\n return (\n <Fragment key={visibility}>\n {index > 0 ? (\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n as=\"span\"\n aria-hidden\n >\n |\n </Text>\n ) : null}\n <VisibilityIcon aria-hidden size={16} />\n <Text variant=\"body-small\" color=\"secondary\" as=\"span\">\n {visibility}\n </Text>\n </Fragment>\n );\n })}\n </div>\n ) : null}\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 </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":["Fragment"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
2
+
3
+ var css_248z = ".ApiCard_card__0055a491ad {\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 /* Hover idiom from dev-ai-hub's ResourceCard: lift + accent-tinted border +\n accent glow shadow. The Apigee plugin has no brand hex of its own, so the\n accent is BUI's info token — the same one the version badge uses. */\n --card-accent: var(--bui-fg-info);\n transition: box-shadow 160ms ease,\n transform 160ms cubic-bezier(0.16, 1, 0.3, 1), border-color 160ms 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__0055a491ad,\na.ApiCard_card__0055a491ad: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__0055a491ad {\n width: 100%;\n font: inherit;\n text-align: left;\n cursor: pointer;\n appearance: none;\n}\n\n.ApiCard_card__0055a491ad:hover {\n border-color: color-mix(in srgb, var(--card-accent) 40%, var(--bui-border-1));\n /* Two-part shadow: a soft ambient one and a tighter contact one tinted with\n the accent. A single large blur reads as a glow rather than as elevation. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 12px 28px -12px color-mix(in srgb, var(--card-accent) 40%, transparent);\n transform: translateY(-2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ApiCard_card__0055a491ad {\n transition: none;\n }\n\n .ApiCard_card__0055a491ad:hover {\n transform: none;\n }\n}\n\n.ApiCard_card__0055a491ad:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n}\n\n.ApiCard_header__0055a491ad {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_titleRow__0055a491ad {\n display: flex;\n align-items: flex-start;\n gap: var(--bui-space-2);\n}\n\n.ApiCard_name__0055a491ad {\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__0055a491ad {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiCard_badge__0055a491ad {\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__0055a491ad {\n background-color: var(--bui-bg-success);\n color: var(--bui-fg-success-on-bg);\n}\n\n.ApiCard_badgeWarning__0055a491ad {\n background-color: var(--bui-bg-warning);\n color: var(--bui-fg-warning-on-bg);\n}\n\n.ApiCard_badgeError__0055a491ad {\n background-color: var(--bui-bg-danger);\n color: var(--bui-fg-danger-on-bg);\n}\n\n.ApiCard_badgeVersion__0055a491ad {\n background-color: var(--bui-bg-info);\n color: var(--bui-fg-info-on-bg);\n text-transform: none;\n}\n\n.ApiCard_body__0055a491ad {\n display: flex;\n align-items: flex-start;\n gap: var(--bui-space-4);\n}\n\n.ApiCard_leftCol__0055a491ad {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-1);\n color: var(--bui-fg-secondary);\n}\n\n.ApiCard_metaItem__0055a491ad {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiCard_description__0055a491ad {\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__0055a491ad","header":"ApiCard_header__0055a491ad","titleRow":"ApiCard_titleRow__0055a491ad","name":"ApiCard_name__0055a491ad","badges":"ApiCard_badges__0055a491ad","badge":"ApiCard_badge__0055a491ad","badgeOk":"ApiCard_badgeOk__0055a491ad","badgeWarning":"ApiCard_badgeWarning__0055a491ad","badgeError":"ApiCard_badgeError__0055a491ad","badgeVersion":"ApiCard_badgeVersion__0055a491ad","body":"ApiCard_body__0055a491ad","leftCol":"ApiCard_leftCol__0055a491ad","metaItem":"ApiCard_metaItem__0055a491ad","description":"ApiCard_description__0055a491ad"};
5
+ styleInject(css_248z);
6
+
7
+ export { styles as default };
8
+ //# sourceMappingURL=ApiCard.module.css.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiCard.module.css.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -0,0 +1,14 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { ItemCardGrid } from '@backstage/core-components';
3
+ import { ApiCard } from './ApiCard.esm.js';
4
+ import { ApiTable } from './ApiTable.esm.js';
5
+
6
+ function ApiCardGrid({ vms, viewMode, onSelect }) {
7
+ if (viewMode === "list") {
8
+ return /* @__PURE__ */ jsx(ApiTable, { vms, onSelect });
9
+ }
10
+ return /* @__PURE__ */ jsx(ItemCardGrid, { children: vms.map((vm) => /* @__PURE__ */ jsx(ApiCard, { vm, onSelect }, vm.targetRef.name)) });
11
+ }
12
+
13
+ export { ApiCardGrid };
14
+ //# sourceMappingURL=ApiCardGrid.esm.js.map
@@ -0,0 +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 { 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;;;;"}