@nospt/backstage-plugin-apigee 0.1.0

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 (41) hide show
  1. package/README.md +19 -0
  2. package/dist/components/ApiCard.esm.js +146 -0
  3. package/dist/components/ApiCard.esm.js.map +1 -0
  4. package/dist/components/ApiCardGrid.esm.js +56 -0
  5. package/dist/components/ApiCardGrid.esm.js.map +1 -0
  6. package/dist/components/ApigeeApiCatalogPage.esm.js +139 -0
  7. package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -0
  8. package/dist/components/FilterToolbar.esm.js +197 -0
  9. package/dist/components/FilterToolbar.esm.js.map +1 -0
  10. package/dist/components/SearchBar.esm.js +48 -0
  11. package/dist/components/SearchBar.esm.js.map +1 -0
  12. package/dist/components/apiCardViewModel.esm.js +41 -0
  13. package/dist/components/apiCardViewModel.esm.js.map +1 -0
  14. package/dist/components/apigeeFacetFilters.esm.js +61 -0
  15. package/dist/components/apigeeFacetFilters.esm.js.map +1 -0
  16. package/dist/components/apigeeOrgFilter.esm.js +11 -0
  17. package/dist/components/apigeeOrgFilter.esm.js.map +1 -0
  18. package/dist/components/apigeeSearchParams.esm.js +20 -0
  19. package/dist/components/apigeeSearchParams.esm.js.map +1 -0
  20. package/dist/components/categoryIcons.esm.js +63 -0
  21. package/dist/components/categoryIcons.esm.js.map +1 -0
  22. package/dist/components/facetModel.esm.js +78 -0
  23. package/dist/components/facetModel.esm.js.map +1 -0
  24. package/dist/components/grouping.esm.js +31 -0
  25. package/dist/components/grouping.esm.js.map +1 -0
  26. package/dist/components/states.esm.js +95 -0
  27. package/dist/components/states.esm.js.map +1 -0
  28. package/dist/components/useApigeeFilters.esm.js +86 -0
  29. package/dist/components/useApigeeFilters.esm.js.map +1 -0
  30. package/dist/components/useDebouncedValue.esm.js +13 -0
  31. package/dist/components/useDebouncedValue.esm.js.map +1 -0
  32. package/dist/constants.esm.js +8 -0
  33. package/dist/constants.esm.js.map +1 -0
  34. package/dist/index.d.ts +66 -0
  35. package/dist/index.esm.js +2 -0
  36. package/dist/index.esm.js.map +1 -0
  37. package/dist/plugin.esm.js +33 -0
  38. package/dist/plugin.esm.js.map +1 -0
  39. package/dist/routes.esm.js +6 -0
  40. package/dist/routes.esm.js.map +1 -0
  41. package/package.json +89 -0
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @nospt/backstage-plugin-apigee
2
+
3
+ Frontend package for the bootstrap stage of the Backstage Apigee plugin.
4
+
5
+ ## Included
6
+
7
+ - New Frontend System plugin registration
8
+ - Placeholder page under `/apigee/bootstrap`
9
+ - Shared permission exports from the common package
10
+
11
+ ## Development
12
+
13
+ ```bash
14
+ cd plugins/apigee
15
+ yarn start
16
+ yarn test
17
+ yarn lint
18
+ yarn build
19
+ ```
@@ -0,0 +1,146 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Link, StatusOK, StatusError, StatusWarning } from '@backstage/core-components';
3
+ import { useRouteRef } from '@backstage/frontend-plugin-api';
4
+ import { entityRouteRef } from '@backstage/plugin-catalog-react';
5
+ import { CardContent, Typography, Tooltip, Card, CardActionArea, makeStyles } from '@material-ui/core';
6
+ import DescriptionOutlined from '@material-ui/icons/DescriptionOutlined';
7
+ import GroupOutlined from '@material-ui/icons/GroupOutlined';
8
+
9
+ const useStyles = makeStyles((theme) => ({
10
+ card: {
11
+ height: "100%"
12
+ },
13
+ action: {
14
+ height: "100%",
15
+ minHeight: 172,
16
+ alignItems: "stretch",
17
+ transition: theme.transitions.create(["box-shadow"]),
18
+ "&:focus-visible": {
19
+ outline: `2px solid ${theme.palette.primary.main}`,
20
+ outlineOffset: 2
21
+ },
22
+ "&:hover": {
23
+ boxShadow: theme.shadows[4]
24
+ },
25
+ "@media (prefers-reduced-motion: reduce)": {
26
+ transition: "none"
27
+ }
28
+ },
29
+ content: {
30
+ display: "flex",
31
+ flexDirection: "column",
32
+ gap: theme.spacing(1),
33
+ height: "100%"
34
+ },
35
+ header: {
36
+ display: "flex",
37
+ alignItems: "flex-start",
38
+ gap: theme.spacing(1)
39
+ },
40
+ categoryIcon: {
41
+ color: theme.palette.text.secondary,
42
+ marginTop: 2
43
+ },
44
+ name: {
45
+ flex: 1,
46
+ display: "-webkit-box",
47
+ WebkitLineClamp: 2,
48
+ WebkitBoxOrient: "vertical",
49
+ overflow: "hidden",
50
+ wordBreak: "break-word"
51
+ },
52
+ meta: {
53
+ display: "flex",
54
+ alignItems: "center",
55
+ gap: theme.spacing(0.5),
56
+ color: theme.palette.text.secondary
57
+ },
58
+ metaIcon: {
59
+ fontSize: 16
60
+ },
61
+ version: {
62
+ marginLeft: "auto"
63
+ },
64
+ description: {
65
+ display: "-webkit-box",
66
+ WebkitLineClamp: 2,
67
+ WebkitBoxOrient: "vertical",
68
+ overflow: "hidden",
69
+ color: theme.palette.text.secondary
70
+ },
71
+ footer: {
72
+ marginTop: "auto",
73
+ display: "flex",
74
+ alignItems: "center",
75
+ gap: theme.spacing(1)
76
+ },
77
+ specIcon: {
78
+ display: "inline-flex",
79
+ color: theme.palette.text.secondary
80
+ }
81
+ }));
82
+ function LifecycleBadge({
83
+ kind,
84
+ label
85
+ }) {
86
+ if (kind === "ok") {
87
+ return /* @__PURE__ */ jsx(StatusOK, { children: label });
88
+ }
89
+ if (kind === "error") {
90
+ return /* @__PURE__ */ jsx(StatusError, { children: label });
91
+ }
92
+ return /* @__PURE__ */ jsx(StatusWarning, { children: label });
93
+ }
94
+ function ApiCard({ vm, icon: Icon }) {
95
+ const classes = useStyles();
96
+ const entityRoute = useRouteRef(entityRouteRef);
97
+ const { kind, namespace, name } = vm.targetRef;
98
+ const href = entityRoute ? entityRoute({ kind: kind.toLowerCase(), namespace, name }) : void 0;
99
+ const ariaLabel = `Open ${vm.name} API details`;
100
+ const content = /* @__PURE__ */ jsxs(CardContent, { className: classes.content, children: [
101
+ /* @__PURE__ */ jsxs("div", { className: classes.header, children: [
102
+ Icon ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: classes.categoryIcon, fontSize: "small" }) : null,
103
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", component: "h3", className: classes.name, children: vm.name }),
104
+ /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label })
105
+ ] }),
106
+ /* @__PURE__ */ jsxs("div", { className: classes.meta, children: [
107
+ /* @__PURE__ */ jsx(GroupOutlined, { "aria-hidden": true, className: classes.metaIcon }),
108
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", component: "span", children: vm.ownerLabel }),
109
+ vm.version ? /* @__PURE__ */ jsxs(
110
+ Typography,
111
+ {
112
+ variant: "body2",
113
+ component: "span",
114
+ className: classes.version,
115
+ children: [
116
+ "v",
117
+ vm.version
118
+ ]
119
+ }
120
+ ) : null
121
+ ] }),
122
+ vm.description ? /* @__PURE__ */ jsx(Typography, { variant: "body2", className: classes.description, children: vm.description }) : null,
123
+ vm.hasSpec ? /* @__PURE__ */ jsx("div", { className: classes.footer, children: /* @__PURE__ */ jsx(Tooltip, { title: "OpenAPI spec available", children: /* @__PURE__ */ jsx(
124
+ "span",
125
+ {
126
+ role: "img",
127
+ "aria-label": "OpenAPI spec available",
128
+ className: classes.specIcon,
129
+ children: /* @__PURE__ */ jsx(DescriptionOutlined, { "aria-hidden": true, fontSize: "small" })
130
+ }
131
+ ) }) }) : null
132
+ ] });
133
+ return /* @__PURE__ */ jsx(Card, { className: classes.card, variant: "outlined", children: href ? /* @__PURE__ */ jsx(
134
+ CardActionArea,
135
+ {
136
+ component: Link,
137
+ to: href,
138
+ className: classes.action,
139
+ "aria-label": ariaLabel,
140
+ children: content
141
+ }
142
+ ) : /* @__PURE__ */ jsx(CardActionArea, { className: classes.action, "aria-label": ariaLabel, children: content }) });
143
+ }
144
+
145
+ export { ApiCard };
146
+ //# 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\r\nimport {\r\n Link,\r\n StatusError,\r\n StatusOK,\r\n StatusWarning,\r\n} from '@backstage/core-components';\r\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\r\nimport { entityRouteRef } from '@backstage/plugin-catalog-react';\r\nimport {\r\n Card,\r\n CardActionArea,\r\n CardContent,\r\n Tooltip,\r\n Typography,\r\n makeStyles,\r\n} from '@material-ui/core';\r\nimport DescriptionOutlined from '@material-ui/icons/DescriptionOutlined';\r\nimport GroupOutlined from '@material-ui/icons/GroupOutlined';\r\nimport type { SvgIconComponent } from '@material-ui/icons';\r\nimport type { ApiCardViewModel, LifecycleBadgeKind } from './apiCardViewModel';\r\n\r\nexport interface ApiCardProps {\r\n vm: ApiCardViewModel;\r\n /** Category icon resolved by the parent grid (05-05); decorative. */\r\n icon?: SvgIconComponent;\r\n}\r\n\r\nconst useStyles = makeStyles(theme => ({\r\n card: {\r\n height: '100%',\r\n },\r\n action: {\r\n height: '100%',\r\n minHeight: 172,\r\n alignItems: 'stretch',\r\n transition: theme.transitions.create(['box-shadow']),\r\n '&:focus-visible': {\r\n outline: `2px solid ${theme.palette.primary.main}`,\r\n outlineOffset: 2,\r\n },\r\n '&:hover': {\r\n boxShadow: theme.shadows[4],\r\n },\r\n '@media (prefers-reduced-motion: reduce)': {\r\n transition: 'none',\r\n },\r\n },\r\n content: {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: theme.spacing(1),\r\n height: '100%',\r\n },\r\n header: {\r\n display: 'flex',\r\n alignItems: 'flex-start',\r\n gap: theme.spacing(1),\r\n },\r\n categoryIcon: {\r\n color: theme.palette.text.secondary,\r\n marginTop: 2,\r\n },\r\n name: {\r\n flex: 1,\r\n display: '-webkit-box',\r\n WebkitLineClamp: 2,\r\n WebkitBoxOrient: 'vertical',\r\n overflow: 'hidden',\r\n wordBreak: 'break-word',\r\n },\r\n meta: {\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: theme.spacing(0.5),\r\n color: theme.palette.text.secondary,\r\n },\r\n metaIcon: {\r\n fontSize: 16,\r\n },\r\n version: {\r\n marginLeft: 'auto',\r\n },\r\n description: {\r\n display: '-webkit-box',\r\n WebkitLineClamp: 2,\r\n WebkitBoxOrient: 'vertical',\r\n overflow: 'hidden',\r\n color: theme.palette.text.secondary,\r\n },\r\n footer: {\r\n marginTop: 'auto',\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: theme.spacing(1),\r\n },\r\n specIcon: {\r\n display: 'inline-flex',\r\n color: theme.palette.text.secondary,\r\n },\r\n}));\r\n\r\nfunction LifecycleBadge({\r\n kind,\r\n label,\r\n}: {\r\n kind: LifecycleBadgeKind;\r\n label: string;\r\n}) {\r\n if (kind === 'ok') {\r\n return <StatusOK>{label}</StatusOK>;\r\n }\r\n if (kind === 'error') {\r\n return <StatusError>{label}</StatusError>;\r\n }\r\n return <StatusWarning>{label}</StatusWarning>;\r\n}\r\n\r\n/**\r\n * A single Apigee API tile. The whole card is one anchor-based control whose href is\r\n * resolved via `entityRouteRef` (never hardcoded — TR04); it degrades to a non-link\r\n * button when the route ref is unbound (dev harness). Entity text renders as React\r\n * children only — never via raw HTML injection (T-05-03). (UI-02, UI-03)\r\n */\r\nexport function ApiCard({ vm, icon: Icon }: ApiCardProps) {\r\n const classes = useStyles();\r\n const entityRoute = useRouteRef(entityRouteRef);\r\n const { kind, namespace, name } = vm.targetRef;\r\n const href = entityRoute ? entityRoute({ kind: kind.toLowerCase(), namespace, name }) : undefined;\r\n const ariaLabel = `Open ${vm.name} API details`;\r\n\r\n const content = (\r\n <CardContent className={classes.content}>\r\n <div className={classes.header}>\r\n {Icon ? (\r\n <Icon aria-hidden className={classes.categoryIcon} fontSize=\"small\" />\r\n ) : null}\r\n <Typography variant=\"subtitle1\" component=\"h3\" className={classes.name}>\r\n {vm.name}\r\n </Typography>\r\n <LifecycleBadge kind={vm.badge.kind} label={vm.badge.label} />\r\n </div>\r\n\r\n <div className={classes.meta}>\r\n <GroupOutlined aria-hidden className={classes.metaIcon} />\r\n <Typography variant=\"body2\" component=\"span\">\r\n {vm.ownerLabel}\r\n </Typography>\r\n {vm.version ? (\r\n <Typography\r\n variant=\"body2\"\r\n component=\"span\"\r\n className={classes.version}\r\n >\r\n v{vm.version}\r\n </Typography>\r\n ) : null}\r\n </div>\r\n\r\n {vm.description ? (\r\n <Typography variant=\"body2\" className={classes.description}>\r\n {vm.description}\r\n </Typography>\r\n ) : null}\r\n\r\n {vm.hasSpec ? (\r\n <div className={classes.footer}>\r\n <Tooltip title=\"OpenAPI spec available\">\r\n <span\r\n role=\"img\"\r\n aria-label=\"OpenAPI spec available\"\r\n className={classes.specIcon}\r\n >\r\n <DescriptionOutlined aria-hidden fontSize=\"small\" />\r\n </span>\r\n </Tooltip>\r\n </div>\r\n ) : null}\r\n </CardContent>\r\n );\r\n\r\n return (\r\n <Card className={classes.card} variant=\"outlined\">\r\n {href ? (\r\n <CardActionArea\r\n component={Link}\r\n to={href}\r\n className={classes.action}\r\n aria-label={ariaLabel}\r\n >\r\n {content}\r\n </CardActionArea>\r\n ) : (\r\n <CardActionArea className={classes.action} aria-label={ariaLabel}>\r\n {content}\r\n </CardActionArea>\r\n )}\r\n </Card>\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { ItemCardGrid } from '@backstage/core-components';
3
+ import { Typography, Chip, makeStyles } from '@material-ui/core';
4
+ import { ApiCard } from './ApiCard.esm.js';
5
+ import { resolveCategoryIcon } from './categoryIcons.esm.js';
6
+ import { groupByCategory } from './grouping.esm.js';
7
+
8
+ const useStyles = makeStyles((theme) => ({
9
+ section: {
10
+ marginBottom: theme.spacing(4)
11
+ },
12
+ sectionHeader: {
13
+ display: "flex",
14
+ alignItems: "center",
15
+ gap: theme.spacing(1),
16
+ marginBottom: theme.spacing(2)
17
+ },
18
+ sectionIcon: {
19
+ color: theme.palette.text.secondary
20
+ }
21
+ }));
22
+ function ApiCardGrid({ vms, viewMode, overrides }) {
23
+ const classes = useStyles();
24
+ if (viewMode === "flat") {
25
+ return /* @__PURE__ */ jsx(ItemCardGrid, { children: vms.map((vm) => /* @__PURE__ */ jsx(
26
+ ApiCard,
27
+ {
28
+ vm,
29
+ icon: resolveCategoryIcon(vm.category, overrides)
30
+ },
31
+ vm.targetRef.name
32
+ )) });
33
+ }
34
+ const groups = groupByCategory(vms);
35
+ return /* @__PURE__ */ jsx(Fragment, { children: groups.map((group) => {
36
+ const SectionIcon = resolveCategoryIcon(group.key, overrides);
37
+ return /* @__PURE__ */ jsxs("section", { className: classes.section, children: [
38
+ /* @__PURE__ */ jsxs("div", { className: classes.sectionHeader, children: [
39
+ /* @__PURE__ */ jsx(SectionIcon, { "aria-hidden": true, className: classes.sectionIcon }),
40
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", component: "h2", children: group.label }),
41
+ /* @__PURE__ */ jsx(Chip, { size: "small", label: group.items.length })
42
+ ] }),
43
+ /* @__PURE__ */ jsx(ItemCardGrid, { children: group.items.map((vm) => /* @__PURE__ */ jsx(
44
+ ApiCard,
45
+ {
46
+ vm,
47
+ icon: resolveCategoryIcon(vm.category, overrides)
48
+ },
49
+ vm.targetRef.name
50
+ )) })
51
+ ] }, group.key);
52
+ }) });
53
+ }
54
+
55
+ export { ApiCardGrid };
56
+ //# 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\r\nimport { ItemCardGrid } from '@backstage/core-components';\r\nimport { Chip, Typography, makeStyles } from '@material-ui/core';\r\nimport { ApiCard } from './ApiCard';\r\nimport type { ApiCardViewModel } from './apiCardViewModel';\r\nimport { resolveCategoryIcon } from './categoryIcons';\r\nimport { groupByCategory } from './grouping';\r\n\r\nexport type ViewMode = 'flat' | 'grouped';\r\n\r\nexport interface ApiCardGridProps {\r\n vms: ApiCardViewModel[];\r\n viewMode: ViewMode;\r\n /** Raw `apigee.catalog.categoryIcons` override map (slug -> IconKey string). */\r\n overrides?: Record<string, string>;\r\n}\r\n\r\nconst useStyles = makeStyles(theme => ({\r\n section: {\r\n marginBottom: theme.spacing(4),\r\n },\r\n sectionHeader: {\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: theme.spacing(1),\r\n marginBottom: theme.spacing(2),\r\n },\r\n sectionIcon: {\r\n color: theme.palette.text.secondary,\r\n },\r\n}));\r\n\r\n/**\r\n * Presentational grid of `ApiCard` tiles. In `flat` mode a single responsive grid; in\r\n * `grouped` mode one section per category (icon + label + count chip) with the \"Other\"\r\n * bucket last (ordering enforced by `groupByCategory`). No data fetching. (UI-01, UI-11)\r\n */\r\nexport function ApiCardGrid({ vms, viewMode, overrides }: ApiCardGridProps) {\r\n const classes = useStyles();\r\n\r\n if (viewMode === 'flat') {\r\n return (\r\n <ItemCardGrid>\r\n {vms.map(vm => (\r\n <ApiCard\r\n key={vm.targetRef.name}\r\n vm={vm}\r\n icon={resolveCategoryIcon(vm.category, overrides)}\r\n />\r\n ))}\r\n </ItemCardGrid>\r\n );\r\n }\r\n\r\n const groups = groupByCategory(vms);\r\n return (\r\n <>\r\n {groups.map(group => {\r\n const SectionIcon = resolveCategoryIcon(group.key, overrides);\r\n return (\r\n <section key={group.key} className={classes.section}>\r\n <div className={classes.sectionHeader}>\r\n <SectionIcon aria-hidden className={classes.sectionIcon} />\r\n <Typography variant=\"h5\" component=\"h2\">\r\n {group.label}\r\n </Typography>\r\n <Chip size=\"small\" label={group.items.length} />\r\n </div>\r\n <ItemCardGrid>\r\n {group.items.map(vm => (\r\n <ApiCard\r\n key={vm.targetRef.name}\r\n vm={vm}\r\n icon={resolveCategoryIcon(vm.category, overrides)}\r\n />\r\n ))}\r\n </ItemCardGrid>\r\n </section>\r\n );\r\n })}\r\n </>\r\n );\r\n}\r\n"],"names":[],"mappings":";;;;;;;AAiBA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,OAAA,EAAS;AAAA,IACP,YAAA,EAAc,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GAC/B;AAAA,EACA,aAAA,EAAe;AAAA,IACb,OAAA,EAAS,MAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,IACpB,YAAA,EAAc,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GAC/B;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK;AAAA;AAE9B,CAAA,CAAE,CAAA;AAOK,SAAS,WAAA,CAAY,EAAE,GAAA,EAAK,QAAA,EAAU,WAAU,EAAqB;AAC1E,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,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,OAAA,CAAQ,OAAA,EAC1C,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,OAAA,CAAQ,aAAA,EACtB,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,aAAA,EAAW,IAAA,EAAC,SAAA,EAAW,QAAQ,WAAA,EAAa,CAAA;AAAA,4BACxD,UAAA,EAAA,EAAW,OAAA,EAAQ,MAAK,SAAA,EAAU,IAAA,EAChC,gBAAM,KAAA,EACT,CAAA;AAAA,4BACC,IAAA,EAAA,EAAK,IAAA,EAAK,SAAQ,KAAA,EAAO,KAAA,CAAM,MAAM,MAAA,EAAQ;AAAA,OAAA,EAChD,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;;;;"}
@@ -0,0 +1,139 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Page, Header, Content, ContentHeader } from '@backstage/core-components';
3
+ import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
+ import { EntityListProvider, useEntityList } from '@backstage/plugin-catalog-react';
5
+ import { ButtonGroup, Button, makeStyles } from '@material-ui/core';
6
+ import ViewAgenda from '@material-ui/icons/ViewAgenda';
7
+ import ViewModule from '@material-ui/icons/ViewModule';
8
+ import { useEffect, useMemo, useState } from 'react';
9
+ import { toApiCardViewModel } from './apiCardViewModel.esm.js';
10
+ import { ApiCardGrid } from './ApiCardGrid.esm.js';
11
+ import { ApiKindFilter, isApigeeEntity } from './apigeeFacetFilters.esm.js';
12
+ import { ApigeeOrgFilter } from './apigeeOrgFilter.esm.js';
13
+ import { isActive } from './apigeeSearchParams.esm.js';
14
+ import { deriveFacets } from './facetModel.esm.js';
15
+ import { FilterToolbar } from './FilterToolbar.esm.js';
16
+ import { SearchBar } from './SearchBar.esm.js';
17
+ import { ApiCatalogError, SkeletonGrid, NoMatchState, ApiCatalogEmptyState } from './states.esm.js';
18
+ import { useApigeeFilters } from './useApigeeFilters.esm.js';
19
+
20
+ const useStyles = makeStyles({
21
+ toggleGroup: {
22
+ marginLeft: "auto"
23
+ }
24
+ });
25
+ function ApplyApigeeFilters() {
26
+ const { updateFilters } = useEntityList();
27
+ useEffect(() => {
28
+ updateFilters({
29
+ apigeeKind: new ApiKindFilter(),
30
+ apigeeOrg: new ApigeeOrgFilter()
31
+ });
32
+ }, [updateFilters]);
33
+ return null;
34
+ }
35
+ function ApigeeCatalogContent() {
36
+ const classes = useStyles();
37
+ const { entities, backendEntities, loading, error } = useEntityList();
38
+ const { state, inputValue, setInputValue, toggleFacet, clearFacet, clearAll } = useApigeeFilters();
39
+ const configApi = useApi(configApiRef);
40
+ const overrides = useMemo(() => {
41
+ const config = configApi.getOptionalConfig("apigee.catalog.categoryIcons");
42
+ if (!config) {
43
+ return void 0;
44
+ }
45
+ const out = {};
46
+ for (const key of config.keys()) {
47
+ const value = config.getOptionalString(key);
48
+ if (value) {
49
+ out[key] = value;
50
+ }
51
+ }
52
+ return Object.keys(out).length > 0 ? out : void 0;
53
+ }, [configApi]);
54
+ const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);
55
+ const facets = useMemo(
56
+ () => deriveFacets(backendEntities.filter(isApigeeEntity), overrides),
57
+ [backendEntities, overrides]
58
+ );
59
+ const active = isActive(state);
60
+ const [viewMode, setViewMode] = useState("flat");
61
+ const renderBody = () => {
62
+ if (error) {
63
+ return /* @__PURE__ */ jsx(ApiCatalogError, { error });
64
+ }
65
+ if (loading) {
66
+ return /* @__PURE__ */ jsx(SkeletonGrid, {});
67
+ }
68
+ if (vms.length === 0) {
69
+ return active ? /* @__PURE__ */ jsx(NoMatchState, { onClearAll: clearAll }) : /* @__PURE__ */ jsx(ApiCatalogEmptyState, {});
70
+ }
71
+ return /* @__PURE__ */ jsx(ApiCardGrid, { vms, viewMode, overrides });
72
+ };
73
+ return /* @__PURE__ */ jsxs(Page, { themeId: "apis", children: [
74
+ /* @__PURE__ */ jsx(
75
+ Header,
76
+ {
77
+ title: "API Catalog",
78
+ subtitle: "Browse APIs discovered from Apigee & API Hub"
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsxs(Content, { children: [
82
+ /* @__PURE__ */ jsx(ContentHeader, { title: `${vms.length} APIs`, children: /* @__PURE__ */ jsx(
83
+ "div",
84
+ {
85
+ role: "group",
86
+ "aria-label": "API view mode",
87
+ className: classes.toggleGroup,
88
+ children: /* @__PURE__ */ jsxs(ButtonGroup, { size: "small", children: [
89
+ /* @__PURE__ */ jsx(
90
+ Button,
91
+ {
92
+ startIcon: /* @__PURE__ */ jsx(ViewModule, {}),
93
+ "aria-label": "Flat grid view",
94
+ "aria-pressed": viewMode === "flat",
95
+ color: viewMode === "flat" ? "primary" : "default",
96
+ onClick: () => setViewMode("flat"),
97
+ children: "Flat"
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx(
101
+ Button,
102
+ {
103
+ startIcon: /* @__PURE__ */ jsx(ViewAgenda, {}),
104
+ "aria-label": "Group by category",
105
+ "aria-pressed": viewMode === "grouped",
106
+ color: viewMode === "grouped" ? "primary" : "default",
107
+ onClick: () => setViewMode("grouped"),
108
+ children: "Grouped"
109
+ }
110
+ )
111
+ ] })
112
+ }
113
+ ) }),
114
+ !loading && !error && backendEntities.filter(isApigeeEntity).length > 0 ? /* @__PURE__ */ jsx(
115
+ FilterToolbar,
116
+ {
117
+ facets,
118
+ selected: state.selected,
119
+ onToggleFacet: toggleFacet,
120
+ onClearFacet: clearFacet,
121
+ onClearAll: clearAll,
122
+ active,
123
+ resultCount: vms.length,
124
+ searchBar: /* @__PURE__ */ jsx(SearchBar, { value: inputValue, onChange: setInputValue })
125
+ }
126
+ ) : null,
127
+ renderBody()
128
+ ] })
129
+ ] });
130
+ }
131
+ function ApigeeApiCatalogPage() {
132
+ return /* @__PURE__ */ jsxs(EntityListProvider, { children: [
133
+ /* @__PURE__ */ jsx(ApplyApigeeFilters, {}),
134
+ /* @__PURE__ */ jsx(ApigeeCatalogContent, {})
135
+ ] });
136
+ }
137
+
138
+ export { ApigeeApiCatalogPage, ApigeeCatalogContent };
139
+ //# sourceMappingURL=ApigeeApiCatalogPage.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApigeeApiCatalogPage.esm.js","sources":["../../src/components/ApigeeApiCatalogPage.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\r\nimport { Content, ContentHeader, Header, Page } from '@backstage/core-components';\r\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\r\nimport {\r\n EntityListProvider,\r\n useEntityList,\r\n type DefaultEntityFilters,\r\n} from '@backstage/plugin-catalog-react';\r\nimport { Button, ButtonGroup, makeStyles } from '@material-ui/core';\r\nimport ViewAgenda from '@material-ui/icons/ViewAgenda';\r\nimport ViewModule from '@material-ui/icons/ViewModule';\r\nimport { useEffect, useMemo, useState } from 'react';\r\nimport { toApiCardViewModel } from './apiCardViewModel';\r\nimport { ApiCardGrid, type ViewMode } from './ApiCardGrid';\r\nimport {\r\n ApiKindFilter,\r\n isApigeeEntity,\r\n type ApiSearchFilter,\r\n type FieldFacetFilter,\r\n} from './apigeeFacetFilters';\r\nimport { ApigeeOrgFilter } from './apigeeOrgFilter';\r\nimport { isActive } from './apigeeSearchParams';\r\nimport { deriveFacets } from './facetModel';\r\nimport { FilterToolbar } from './FilterToolbar';\r\nimport { SearchBar } from './SearchBar';\r\nimport {\r\n ApiCatalogEmptyState,\r\n ApiCatalogError,\r\n NoMatchState,\r\n SkeletonGrid,\r\n} from './states';\r\nimport { useApigeeFilters } from './useApigeeFilters';\r\n\r\n/**\r\n * Entity-list filters registered on the Apigee catalog page. `apigeeKind` (the seam-free\r\n * `ApiKindFilter`) and `apigeeOrg` (the dual-key is-Apigee gate) are the backend filters applied\r\n * once by {@link ApplyApigeeFilters}; the facet + search filters\r\n * (`apigeeLifecycle`/`apigeeOrgFacet`/`apigeeOwner`/`apigeeCategory`/`apigeeSearch`) are driven from\r\n * the URL by `useApigeeFilters`. Exported so the hook can import the filter contract.\r\n */\r\nexport type ApigeeFilters = DefaultEntityFilters & {\r\n apigeeKind?: ApiKindFilter;\r\n apigeeOrg?: ApigeeOrgFilter;\r\n apigeeLifecycle?: FieldFacetFilter;\r\n apigeeOrgFacet?: FieldFacetFilter;\r\n apigeeOwner?: FieldFacetFilter;\r\n apigeeCategory?: FieldFacetFilter;\r\n apigeeSearch?: ApiSearchFilter;\r\n};\r\n\r\nconst useStyles = makeStyles({\r\n toggleGroup: {\r\n marginLeft: 'auto',\r\n },\r\n});\r\n\r\n/**\r\n * Registers the Apigee backend filters on the shared EntityListProvider ONCE: the seam-free\r\n * `ApiKindFilter` (kind=api with NO `toQueryValue`, so the provider's URL writer stays a no-op and\r\n * `useSearchParams` owns a clean URL) plus the dual-key `ApigeeOrgFilter` is-Apigee gate. The facet\r\n * + search filters are applied separately, URL-driven, by `useApigeeFilters`. Rendered inside the\r\n * provider; renders nothing itself.\r\n */\r\nfunction ApplyApigeeFilters() {\r\n const { updateFilters } = useEntityList<ApigeeFilters>();\r\n useEffect(() => {\r\n updateFilters({\r\n apigeeKind: new ApiKindFilter(),\r\n apigeeOrg: new ApigeeOrgFilter(),\r\n });\r\n }, [updateFilters]);\r\n return null;\r\n}\r\n\r\n/**\r\n * The page body: reads the filtered entity list, maps to view-models, and branches on\r\n * loading/empty/error. Exposes a Flat/Grouped view toggle (component state only, no view\r\n * URL param — that is a Phase 6 seam). (UI-01, UI-05, UI-11)\r\n */\r\nexport function ApigeeCatalogContent() {\r\n const classes = useStyles();\r\n const { entities, backendEntities, loading, error } =\r\n useEntityList<ApigeeFilters>();\r\n const { state, inputValue, setInputValue, toggleFacet, clearFacet, clearAll } =\r\n useApigeeFilters();\r\n const configApi = useApi(configApiRef);\r\n\r\n const overrides = useMemo<Record<string, string> | undefined>(() => {\r\n const config = configApi.getOptionalConfig('apigee.catalog.categoryIcons');\r\n if (!config) {\r\n return undefined;\r\n }\r\n const out: Record<string, string> = {};\r\n for (const key of config.keys()) {\r\n const value = config.getOptionalString(key);\r\n if (value) {\r\n out[key] = value;\r\n }\r\n }\r\n return Object.keys(out).length > 0 ? out : undefined;\r\n }, [configApi]);\r\n\r\n const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);\r\n const facets = useMemo(\r\n () => deriveFacets(backendEntities.filter(isApigeeEntity), overrides),\r\n [backendEntities, overrides],\r\n );\r\n const active = isActive(state);\r\n const [viewMode, setViewMode] = useState<ViewMode>('flat');\r\n\r\n const renderBody = () => {\r\n if (error) {\r\n return <ApiCatalogError error={error} />;\r\n }\r\n if (loading) {\r\n return <SkeletonGrid />;\r\n }\r\n if (vms.length === 0) {\r\n return active ? (\r\n <NoMatchState onClearAll={clearAll} />\r\n ) : (\r\n <ApiCatalogEmptyState />\r\n );\r\n }\r\n return <ApiCardGrid vms={vms} viewMode={viewMode} overrides={overrides} />;\r\n };\r\n\r\n return (\r\n <Page themeId=\"apis\">\r\n <Header\r\n title=\"API Catalog\"\r\n subtitle=\"Browse APIs discovered from Apigee & API Hub\"\r\n />\r\n <Content>\r\n <ContentHeader title={`${vms.length} APIs`}>\r\n <div\r\n role=\"group\"\r\n aria-label=\"API view mode\"\r\n className={classes.toggleGroup}\r\n >\r\n <ButtonGroup size=\"small\">\r\n <Button\r\n startIcon={<ViewModule />}\r\n aria-label=\"Flat grid view\"\r\n aria-pressed={viewMode === 'flat'}\r\n color={viewMode === 'flat' ? 'primary' : 'default'}\r\n onClick={() => setViewMode('flat')}\r\n >\r\n Flat\r\n </Button>\r\n <Button\r\n startIcon={<ViewAgenda />}\r\n aria-label=\"Group by category\"\r\n aria-pressed={viewMode === 'grouped'}\r\n color={viewMode === 'grouped' ? 'primary' : 'default'}\r\n onClick={() => setViewMode('grouped')}\r\n >\r\n Grouped\r\n </Button>\r\n </ButtonGroup>\r\n </div>\r\n </ContentHeader>\r\n {!loading &&\r\n !error &&\r\n backendEntities.filter(isApigeeEntity).length > 0 ? (\r\n <FilterToolbar\r\n facets={facets}\r\n selected={state.selected}\r\n onToggleFacet={toggleFacet}\r\n onClearFacet={clearFacet}\r\n onClearAll={clearAll}\r\n active={active}\r\n resultCount={vms.length}\r\n searchBar={\r\n <SearchBar value={inputValue} onChange={setInputValue} />\r\n }\r\n />\r\n ) : null}\r\n {renderBody()}\r\n </Content>\r\n </Page>\r\n );\r\n}\r\n\r\n/**\r\n * Route target for `/apigee` (05-06). Wraps the content in an `EntityListProvider` and\r\n * registers the Apigee filters — the shared filter context Phase 6's facets + search reuse.\r\n */\r\nexport function ApigeeApiCatalogPage() {\r\n return (\r\n <EntityListProvider>\r\n <ApplyApigeeFilters />\r\n <ApigeeCatalogContent />\r\n </EntityListProvider>\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import { Box, Button, makeStyles, Chip, Menu, FormGroup, FormControlLabel, Checkbox } from '@material-ui/core';
4
+ import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
5
+ import Clear from '@material-ui/icons/Clear';
6
+
7
+ const useStyles = makeStyles((theme) => ({
8
+ toolbar: {
9
+ display: "flex",
10
+ flexWrap: "wrap",
11
+ alignItems: "center",
12
+ gap: theme.spacing(1),
13
+ marginBottom: theme.spacing(2)
14
+ },
15
+ control: {
16
+ "&:focus-visible": {
17
+ outline: `2px solid ${theme.palette.primary.main}`,
18
+ outlineOffset: 2
19
+ }
20
+ },
21
+ clearAll: {
22
+ marginLeft: "auto"
23
+ },
24
+ countChip: {
25
+ marginLeft: theme.spacing(0.5)
26
+ },
27
+ formGroup: {
28
+ padding: theme.spacing(0, 2)
29
+ },
30
+ menuFooter: {
31
+ display: "flex",
32
+ justifyContent: "flex-end",
33
+ padding: theme.spacing(0.5, 1)
34
+ },
35
+ optionLabel: {
36
+ display: "flex",
37
+ alignItems: "center",
38
+ gap: theme.spacing(0.5)
39
+ },
40
+ optionIcon: {
41
+ fontSize: 16
42
+ },
43
+ srOnly: {
44
+ position: "absolute",
45
+ width: 1,
46
+ height: 1,
47
+ padding: 0,
48
+ margin: -1,
49
+ overflow: "hidden",
50
+ clip: "rect(0 0 0 0)",
51
+ whiteSpace: "nowrap",
52
+ border: 0
53
+ }
54
+ }));
55
+ function FacetDropdown({
56
+ facet,
57
+ selected,
58
+ onToggle,
59
+ onClear
60
+ }) {
61
+ const classes = useStyles();
62
+ const [anchorEl, setAnchorEl] = useState(null);
63
+ const open = Boolean(anchorEl);
64
+ const menuId = `facet-menu-${facet.key}`;
65
+ const count = selected.length;
66
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsxs(
68
+ Button,
69
+ {
70
+ className: classes.control,
71
+ variant: "outlined",
72
+ size: "small",
73
+ disabled: !facet.enabled,
74
+ endIcon: /* @__PURE__ */ jsx(ArrowDropDown, {}),
75
+ color: count > 0 ? "primary" : "default",
76
+ "aria-haspopup": "true",
77
+ "aria-expanded": open,
78
+ "aria-controls": open ? menuId : void 0,
79
+ "aria-label": count > 0 ? `${facet.label} filter, ${count} selected` : `${facet.label} filter`,
80
+ onClick: (event) => setAnchorEl(event.currentTarget),
81
+ children: [
82
+ facet.label,
83
+ count > 0 ? /* @__PURE__ */ jsx(
84
+ Chip,
85
+ {
86
+ className: classes.countChip,
87
+ size: "small",
88
+ color: "primary",
89
+ label: count
90
+ }
91
+ ) : null
92
+ ]
93
+ }
94
+ ),
95
+ /* @__PURE__ */ jsxs(
96
+ Menu,
97
+ {
98
+ id: menuId,
99
+ anchorEl,
100
+ open,
101
+ onClose: () => setAnchorEl(null),
102
+ getContentAnchorEl: null,
103
+ anchorOrigin: { vertical: "bottom", horizontal: "left" },
104
+ transformOrigin: { vertical: "top", horizontal: "left" },
105
+ children: [
106
+ /* @__PURE__ */ jsx(
107
+ FormGroup,
108
+ {
109
+ className: classes.formGroup,
110
+ role: "group",
111
+ "aria-label": `${facet.label} options`,
112
+ children: facet.options.map((option) => {
113
+ const OptionIcon = facet.icons?.[option.value];
114
+ return /* @__PURE__ */ jsx(
115
+ FormControlLabel,
116
+ {
117
+ control: /* @__PURE__ */ jsx(
118
+ Checkbox,
119
+ {
120
+ color: "primary",
121
+ checked: selected.includes(option.value),
122
+ onChange: () => onToggle(option.value)
123
+ }
124
+ ),
125
+ label: OptionIcon ? /* @__PURE__ */ jsxs("span", { className: classes.optionLabel, children: [
126
+ /* @__PURE__ */ jsx(
127
+ OptionIcon,
128
+ {
129
+ className: classes.optionIcon,
130
+ "aria-hidden": true
131
+ }
132
+ ),
133
+ option.label
134
+ ] }) : option.label
135
+ },
136
+ option.value
137
+ );
138
+ })
139
+ }
140
+ ),
141
+ count > 0 ? /* @__PURE__ */ jsx("div", { className: classes.menuFooter, children: /* @__PURE__ */ jsx(
142
+ Button,
143
+ {
144
+ className: classes.control,
145
+ variant: "text",
146
+ size: "small",
147
+ onClick: onClear,
148
+ children: "Clear"
149
+ }
150
+ ) }) : null
151
+ ]
152
+ }
153
+ )
154
+ ] });
155
+ }
156
+ function FilterToolbar({
157
+ facets,
158
+ selected,
159
+ onToggleFacet,
160
+ onClearFacet,
161
+ onClearAll,
162
+ active,
163
+ resultCount,
164
+ searchBar
165
+ }) {
166
+ const classes = useStyles();
167
+ return /* @__PURE__ */ jsxs("div", { role: "search", "aria-label": "Filter and search APIs", children: [
168
+ /* @__PURE__ */ jsxs(Box, { className: classes.toolbar, children: [
169
+ facets.map((facet) => /* @__PURE__ */ jsx(
170
+ FacetDropdown,
171
+ {
172
+ facet,
173
+ selected: selected[facet.key],
174
+ onToggle: (value) => onToggleFacet(facet.key, value),
175
+ onClear: () => onClearFacet(facet.key)
176
+ },
177
+ facet.key
178
+ )),
179
+ searchBar,
180
+ active ? /* @__PURE__ */ jsx(
181
+ Button,
182
+ {
183
+ className: `${classes.control} ${classes.clearAll}`,
184
+ variant: "text",
185
+ size: "small",
186
+ startIcon: /* @__PURE__ */ jsx(Clear, {}),
187
+ onClick: onClearAll,
188
+ children: "Clear all"
189
+ }
190
+ ) : null
191
+ ] }),
192
+ /* @__PURE__ */ jsx("span", { className: classes.srOnly, "aria-live": "polite", children: `${resultCount} APIs match your filters` })
193
+ ] });
194
+ }
195
+
196
+ export { FilterToolbar };
197
+ //# sourceMappingURL=FilterToolbar.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterToolbar.esm.js","sources":["../../src/components/FilterToolbar.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\r\nimport { useState, type ReactNode } from 'react';\r\nimport {\r\n Box,\r\n Button,\r\n Checkbox,\r\n Chip,\r\n FormControlLabel,\r\n FormGroup,\r\n Menu,\r\n makeStyles,\r\n} from '@material-ui/core';\r\nimport ArrowDropDown from '@material-ui/icons/ArrowDropDown';\r\nimport Clear from '@material-ui/icons/Clear';\r\nimport type { DerivedFacet, FacetKey } from './facetModel';\r\n\r\nexport interface FilterToolbarProps {\r\n /** Derived facets from `deriveFacets()`; ALL render (revised D-05) — one with no values is disabled. */\r\n facets: DerivedFacet[];\r\n /** Current selections per facet (hydrated from the URL state in 06-04). */\r\n selected: Record<FacetKey, string[]>;\r\n onToggleFacet: (key: FacetKey, value: string) => void;\r\n /** Per-facet menu-footer \"Clear\" (clears just that facet). */\r\n onClearFacet: (key: FacetKey) => void;\r\n /** Toolbar \"Clear all\" (resets every facet + search). */\r\n onClearAll: () => void;\r\n /** `isActive(state)` — gates the \"Clear all\" affordance. */\r\n active: boolean;\r\n /** Filtered result count for the polite live region (UI-07). */\r\n resultCount: number;\r\n /** `SearchBar` mounted inline in the toolbar row. */\r\n searchBar: ReactNode;\r\n}\r\n\r\nconst useStyles = makeStyles(theme => ({\r\n toolbar: {\r\n display: 'flex',\r\n flexWrap: 'wrap',\r\n alignItems: 'center',\r\n gap: theme.spacing(1),\r\n marginBottom: theme.spacing(2),\r\n },\r\n control: {\r\n '&:focus-visible': {\r\n outline: `2px solid ${theme.palette.primary.main}`,\r\n outlineOffset: 2,\r\n },\r\n },\r\n clearAll: {\r\n marginLeft: 'auto',\r\n },\r\n countChip: {\r\n marginLeft: theme.spacing(0.5),\r\n },\r\n formGroup: {\r\n padding: theme.spacing(0, 2),\r\n },\r\n menuFooter: {\r\n display: 'flex',\r\n justifyContent: 'flex-end',\r\n padding: theme.spacing(0.5, 1),\r\n },\r\n optionLabel: {\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: theme.spacing(0.5),\r\n },\r\n optionIcon: {\r\n fontSize: 16,\r\n },\r\n srOnly: {\r\n position: 'absolute',\r\n width: 1,\r\n height: 1,\r\n padding: 0,\r\n margin: -1,\r\n overflow: 'hidden',\r\n clip: 'rect(0 0 0 0)',\r\n whiteSpace: 'nowrap',\r\n border: 0,\r\n },\r\n}));\r\n\r\n/**\r\n * A single multi-select facet as an outlined menu-button (verified MUI v4 pattern —\r\n * 06-RESEARCH Pattern 7). The `Menu` sets `getContentAnchorEl={null}` so it bottom-anchors,\r\n * and its options are `FormControlLabel`+`Checkbox` inside a `FormGroup` (not clickable menu\r\n * rows) so toggling a checkbox keeps the menu OPEN for multi-select. All dynamic text (`facet.label`,\r\n * `option.label`) renders as React children only — never raw HTML (T-06-03-01).\r\n */\r\nfunction FacetDropdown({\r\n facet,\r\n selected,\r\n onToggle,\r\n onClear,\r\n}: {\r\n facet: DerivedFacet;\r\n selected: string[];\r\n onToggle: (value: string) => void;\r\n onClear: () => void;\r\n}) {\r\n const classes = useStyles();\r\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\r\n const open = Boolean(anchorEl);\r\n const menuId = `facet-menu-${facet.key}`;\r\n const count = selected.length;\r\n\r\n return (\r\n <>\r\n <Button\r\n className={classes.control}\r\n variant=\"outlined\"\r\n size=\"small\"\r\n disabled={!facet.enabled}\r\n endIcon={<ArrowDropDown />}\r\n color={count > 0 ? 'primary' : 'default'}\r\n aria-haspopup=\"true\"\r\n aria-expanded={open}\r\n aria-controls={open ? menuId : undefined}\r\n aria-label={\r\n count > 0\r\n ? `${facet.label} filter, ${count} selected`\r\n : `${facet.label} filter`\r\n }\r\n onClick={event => setAnchorEl(event.currentTarget)}\r\n >\r\n {facet.label}\r\n {count > 0 ? (\r\n <Chip\r\n className={classes.countChip}\r\n size=\"small\"\r\n color=\"primary\"\r\n label={count}\r\n />\r\n ) : null}\r\n </Button>\r\n <Menu\r\n id={menuId}\r\n anchorEl={anchorEl}\r\n open={open}\r\n onClose={() => setAnchorEl(null)}\r\n getContentAnchorEl={null}\r\n anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}\r\n transformOrigin={{ vertical: 'top', horizontal: 'left' }}\r\n >\r\n <FormGroup\r\n className={classes.formGroup}\r\n role=\"group\"\r\n aria-label={`${facet.label} options`}\r\n >\r\n {facet.options.map(option => {\r\n const OptionIcon = facet.icons?.[option.value];\r\n return (\r\n <FormControlLabel\r\n key={option.value}\r\n control={\r\n <Checkbox\r\n color=\"primary\"\r\n checked={selected.includes(option.value)}\r\n onChange={() => onToggle(option.value)}\r\n />\r\n }\r\n label={\r\n OptionIcon ? (\r\n <span className={classes.optionLabel}>\r\n <OptionIcon\r\n className={classes.optionIcon}\r\n aria-hidden\r\n />\r\n {option.label}\r\n </span>\r\n ) : (\r\n option.label\r\n )\r\n }\r\n />\r\n );\r\n })}\r\n </FormGroup>\r\n {count > 0 ? (\r\n <div className={classes.menuFooter}>\r\n <Button\r\n className={classes.control}\r\n variant=\"text\"\r\n size=\"small\"\r\n onClick={onClear}\r\n >\r\n Clear\r\n </Button>\r\n </div>\r\n ) : null}\r\n </Menu>\r\n </>\r\n );\r\n}\r\n\r\n/**\r\n * Presentational Phase 6 toolbar (D-01 top toolbar, D-02 multi-select dropdowns). Renders EVERY\r\n * facet in `FACET_ORDER` (revised D-05 — a facet with no values renders disabled), the inline\r\n * `searchBar` slot, a conditional right-aligned \"Clear all\", and a polite `aria-live` result-count\r\n * region. Purely props-driven — no data fetching and no URL/provider access (that wiring is 06-04).\r\n * (UI-06, UI-07)\r\n */\r\nexport function FilterToolbar({\r\n facets,\r\n selected,\r\n onToggleFacet,\r\n onClearFacet,\r\n onClearAll,\r\n active,\r\n resultCount,\r\n searchBar,\r\n}: FilterToolbarProps) {\r\n const classes = useStyles();\r\n\r\n return (\r\n <div role=\"search\" aria-label=\"Filter and search APIs\">\r\n <Box className={classes.toolbar}>\r\n {facets.map(facet => (\r\n <FacetDropdown\r\n key={facet.key}\r\n facet={facet}\r\n selected={selected[facet.key]}\r\n onToggle={value => onToggleFacet(facet.key, value)}\r\n onClear={() => onClearFacet(facet.key)}\r\n />\r\n ))}\r\n {searchBar}\r\n {active ? (\r\n <Button\r\n className={`${classes.control} ${classes.clearAll}`}\r\n variant=\"text\"\r\n size=\"small\"\r\n startIcon={<Clear />}\r\n onClick={onClearAll}\r\n >\r\n Clear all\r\n </Button>\r\n ) : null}\r\n </Box>\r\n <span className={classes.srOnly} aria-live=\"polite\">\r\n {`${resultCount} APIs match your filters`}\r\n </span>\r\n </div>\r\n );\r\n}\r\n"],"names":[],"mappings":";;;;;;AAkCA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,OAAA,EAAS;AAAA,IACP,OAAA,EAAS,MAAA;AAAA,IACT,QAAA,EAAU,MAAA;AAAA,IACV,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,IACpB,YAAA,EAAc,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GAC/B;AAAA,EACA,OAAA,EAAS;AAAA,IACP,iBAAA,EAAmB;AAAA,MACjB,OAAA,EAAS,CAAA,UAAA,EAAa,KAAA,CAAM,OAAA,CAAQ,QAAQ,IAAI,CAAA,CAAA;AAAA,MAChD,aAAA,EAAe;AAAA;AACjB,GACF;AAAA,EACA,QAAA,EAAU;AAAA,IACR,UAAA,EAAY;AAAA,GACd;AAAA,EACA,SAAA,EAAW;AAAA,IACT,UAAA,EAAY,KAAA,CAAM,OAAA,CAAQ,GAAG;AAAA,GAC/B;AAAA,EACA,SAAA,EAAW;AAAA,IACT,OAAA,EAAS,KAAA,CAAM,OAAA,CAAQ,CAAA,EAAG,CAAC;AAAA,GAC7B;AAAA,EACA,UAAA,EAAY;AAAA,IACV,OAAA,EAAS,MAAA;AAAA,IACT,cAAA,EAAgB,UAAA;AAAA,IAChB,OAAA,EAAS,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,CAAC;AAAA,GAC/B;AAAA,EACA,WAAA,EAAa;AAAA,IACX,OAAA,EAAS,MAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,GAAG;AAAA,GACxB;AAAA,EACA,UAAA,EAAY;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,UAAA;AAAA,IACV,KAAA,EAAO,CAAA;AAAA,IACP,MAAA,EAAQ,CAAA;AAAA,IACR,OAAA,EAAS,CAAA;AAAA,IACT,MAAA,EAAQ,EAAA;AAAA,IACR,QAAA,EAAU,QAAA;AAAA,IACV,IAAA,EAAM,eAAA;AAAA,IACN,UAAA,EAAY,QAAA;AAAA,IACZ,MAAA,EAAQ;AAAA;AAEZ,CAAA,CAAE,CAAA;AASF,SAAS,aAAA,CAAc;AAAA,EACrB,KAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAKG;AACD,EAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA6B,IAAI,CAAA;AACjE,EAAA,MAAM,IAAA,GAAO,QAAQ,QAAQ,CAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,CAAA,WAAA,EAAc,KAAA,CAAM,GAAG,CAAA,CAAA;AACtC,EAAA,MAAM,QAAQ,QAAA,CAAS,MAAA;AAEvB,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,WAAW,OAAA,CAAQ,OAAA;AAAA,QACnB,OAAA,EAAQ,UAAA;AAAA,QACR,IAAA,EAAK,OAAA;AAAA,QACL,QAAA,EAAU,CAAC,KAAA,CAAM,OAAA;AAAA,QACjB,OAAA,sBAAU,aAAA,EAAA,EAAc,CAAA;AAAA,QACxB,KAAA,EAAO,KAAA,GAAQ,CAAA,GAAI,SAAA,GAAY,SAAA;AAAA,QAC/B,eAAA,EAAc,MAAA;AAAA,QACd,eAAA,EAAe,IAAA;AAAA,QACf,eAAA,EAAe,OAAO,MAAA,GAAS,MAAA;AAAA,QAC/B,YAAA,EACE,KAAA,GAAQ,CAAA,GACJ,CAAA,EAAG,KAAA,CAAM,KAAK,CAAA,SAAA,EAAY,KAAK,CAAA,SAAA,CAAA,GAC/B,CAAA,EAAG,KAAA,CAAM,KAAK,CAAA,OAAA,CAAA;AAAA,QAEpB,OAAA,EAAS,CAAA,KAAA,KAAS,WAAA,CAAY,KAAA,CAAM,aAAa,CAAA;AAAA,QAEhD,QAAA,EAAA;AAAA,UAAA,KAAA,CAAM,KAAA;AAAA,UACN,QAAQ,CAAA,mBACP,GAAA;AAAA,YAAC,IAAA;AAAA,YAAA;AAAA,cACC,WAAW,OAAA,CAAQ,SAAA;AAAA,cACnB,IAAA,EAAK,OAAA;AAAA,cACL,KAAA,EAAM,SAAA;AAAA,cACN,KAAA,EAAO;AAAA;AAAA,WACT,GACE;AAAA;AAAA;AAAA,KACN;AAAA,oBACA,IAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,MAAA;AAAA,QACJ,QAAA;AAAA,QACA,IAAA;AAAA,QACA,OAAA,EAAS,MAAM,WAAA,CAAY,IAAI,CAAA;AAAA,QAC/B,kBAAA,EAAoB,IAAA;AAAA,QACpB,YAAA,EAAc,EAAE,QAAA,EAAU,QAAA,EAAU,YAAY,MAAA,EAAO;AAAA,QACvD,eAAA,EAAiB,EAAE,QAAA,EAAU,KAAA,EAAO,YAAY,MAAA,EAAO;AAAA,QAEvD,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,WAAW,OAAA,CAAQ,SAAA;AAAA,cACnB,IAAA,EAAK,OAAA;AAAA,cACL,YAAA,EAAY,CAAA,EAAG,KAAA,CAAM,KAAK,CAAA,QAAA,CAAA;AAAA,cAEzB,QAAA,EAAA,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAC3B,gBAAA,MAAM,UAAA,GAAa,KAAA,CAAM,KAAA,GAAQ,MAAA,CAAO,KAAK,CAAA;AAC7C,gBAAA,uBACE,GAAA;AAAA,kBAAC,gBAAA;AAAA,kBAAA;AAAA,oBAEC,OAAA,kBACE,GAAA;AAAA,sBAAC,QAAA;AAAA,sBAAA;AAAA,wBACC,KAAA,EAAM,SAAA;AAAA,wBACN,OAAA,EAAS,QAAA,CAAS,QAAA,CAAS,MAAA,CAAO,KAAK,CAAA;AAAA,wBACvC,QAAA,EAAU,MAAM,QAAA,CAAS,MAAA,CAAO,KAAK;AAAA;AAAA,qBACvC;AAAA,oBAEF,OACE,UAAA,mBACE,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,QAAQ,WAAA,EACvB,QAAA,EAAA;AAAA,sCAAA,GAAA;AAAA,wBAAC,UAAA;AAAA,wBAAA;AAAA,0BACC,WAAW,OAAA,CAAQ,UAAA;AAAA,0BACnB,aAAA,EAAW;AAAA;AAAA,uBACb;AAAA,sBACC,MAAA,CAAO;AAAA,qBAAA,EACV,IAEA,MAAA,CAAO;AAAA,mBAAA;AAAA,kBAlBN,MAAA,CAAO;AAAA,iBAqBd;AAAA,cAEJ,CAAC;AAAA;AAAA,WACH;AAAA,UACC,QAAQ,CAAA,mBACP,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,QAAQ,UAAA,EACtB,QAAA,kBAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,WAAW,OAAA,CAAQ,OAAA;AAAA,cACnB,OAAA,EAAQ,MAAA;AAAA,cACR,IAAA,EAAK,OAAA;AAAA,cACL,OAAA,EAAS,OAAA;AAAA,cACV,QAAA,EAAA;AAAA;AAAA,aAGH,CAAA,GACE;AAAA;AAAA;AAAA;AACN,GAAA,EACF,CAAA;AAEJ;AASO,SAAS,aAAA,CAAc;AAAA,EAC5B,MAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,QAAA,EAAS,cAAW,wBAAA,EAC5B,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,GAAA,EAAA,EAAI,SAAA,EAAW,OAAA,CAAQ,OAAA,EACrB,QAAA,EAAA;AAAA,MAAA,MAAA,CAAO,IAAI,CAAA,KAAA,qBACV,GAAA;AAAA,QAAC,aAAA;AAAA,QAAA;AAAA,UAEC,KAAA;AAAA,UACA,QAAA,EAAU,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA;AAAA,UAC5B,QAAA,EAAU,CAAA,KAAA,KAAS,aAAA,CAAc,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,UACjD,OAAA,EAAS,MAAM,YAAA,CAAa,KAAA,CAAM,GAAG;AAAA,SAAA;AAAA,QAJhC,KAAA,CAAM;AAAA,OAMd,CAAA;AAAA,MACA,SAAA;AAAA,MACA,MAAA,mBACC,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,WAAW,CAAA,EAAG,OAAA,CAAQ,OAAO,CAAA,CAAA,EAAI,QAAQ,QAAQ,CAAA,CAAA;AAAA,UACjD,OAAA,EAAQ,MAAA;AAAA,UACR,IAAA,EAAK,OAAA;AAAA,UACL,SAAA,sBAAY,KAAA,EAAA,EAAM,CAAA;AAAA,UAClB,OAAA,EAAS,UAAA;AAAA,UACV,QAAA,EAAA;AAAA;AAAA,OAED,GACE;AAAA,KAAA,EACN,CAAA;AAAA,oBACA,GAAA,CAAC,UAAK,SAAA,EAAW,OAAA,CAAQ,QAAQ,WAAA,EAAU,QAAA,EACxC,QAAA,EAAA,CAAA,EAAG,WAAW,CAAA,wBAAA,CAAA,EACjB;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}