From 443d8157b7156dcedbb48c8fa1598f60e9a1b854 Mon Sep 17 00:00:00 2001 From: jaisuriyaNatarajan Date: Tue, 11 Aug 2026 15:58:42 +0000 Subject: [PATCH 1/3] feat(tech-radar): migrate UI from Material UI to Backstage UI Signed-off-by: jaisuriyaNatarajan --- .../.changeset/tech-radar-mui-to-bui.md | 5 + .../tech-radar/packages/app-next/package.json | 1 + .../packages/app-next/src/index.tsx | 1 + .../tech-radar/packages/app/package.json | 1 + .../tech-radar/packages/app/src/index.tsx | 1 + .../tech-radar/plugins/tech-radar/README.md | 2 + .../plugins/tech-radar/dev/index.tsx | 3 + .../plugins/tech-radar/package.json | 3 +- .../RadarBubble/RadarBubble.module.css | 40 ++++++++ .../components/RadarBubble/RadarBubble.tsx | 37 +------ .../RadarDescription.module.css | 36 +++++++ .../RadarDescription/RadarDescription.tsx | 58 +++++------ .../RadarEntry/RadarEntry.module.css | 29 ++++++ .../src/components/RadarEntry/RadarEntry.tsx | 23 +---- .../RadarFooter/RadarFooter.module.css | 24 +++++ .../components/RadarFooter/RadarFooter.tsx | 14 +-- .../components/RadarGrid/RadarGrid.module.css | 38 +++++++ .../src/components/RadarGrid/RadarGrid.tsx | 35 +------ .../RadarLegend/RadarLegend.module.css | 88 +++++++++++++++++ .../components/RadarLegend/RadarLegend.tsx | 70 +------------ .../RadarLegend/RadarLegendLink.tsx | 29 ++---- .../RadarLegend/RadarLegendQuadrant.tsx | 13 ++- .../RadarLegend/RadarLegendRing.tsx | 16 +-- .../src/components/RadarPage.module.css | 32 ++++++ .../tech-radar/src/components/RadarPage.tsx | 51 +++++----- .../RadarTimeline/RadarTimeline.module.css | 53 ++++++++++ .../RadarTimeline/RadarTimeline.tsx | 98 ++++++++----------- .../plugins/tech-radar/src/defaultApi.ts | 36 ++++--- workspaces/tech-radar/yarn.lock | 16 ++- 29 files changed, 529 insertions(+), 324 deletions(-) create mode 100644 workspaces/tech-radar/.changeset/tech-radar-mui-to-bui.md create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarPage.module.css create mode 100644 workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.module.css diff --git a/workspaces/tech-radar/.changeset/tech-radar-mui-to-bui.md b/workspaces/tech-radar/.changeset/tech-radar-mui-to-bui.md new file mode 100644 index 00000000000..61f92639214 --- /dev/null +++ b/workspaces/tech-radar/.changeset/tech-radar-mui-to-bui.md @@ -0,0 +1,5 @@ +--- +'@backstage-community/plugin-tech-radar': major +--- + +**BREAKING** Migrated the Tech Radar plugin UI from Material UI to Backstage UI (`@backstage/ui`). Search, dialogs, typography, layout, and icons no longer depend on `@material-ui/core`. Adopters need `@backstage/ui` styles available in the app (included by default since Backstage `1.41.0`). diff --git a/workspaces/tech-radar/packages/app-next/package.json b/workspaces/tech-radar/packages/app-next/package.json index 937634801e4..724a7f9b9c2 100644 --- a/workspaces/tech-radar/packages/app-next/package.json +++ b/workspaces/tech-radar/packages/app-next/package.json @@ -40,6 +40,7 @@ "@backstage/plugin-signals": "backstage:^", "@backstage/plugin-user-settings": "backstage:^", "@backstage/theme": "backstage:^", + "@backstage/ui": "backstage:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", diff --git a/workspaces/tech-radar/packages/app-next/src/index.tsx b/workspaces/tech-radar/packages/app-next/src/index.tsx index 9784d7314a8..f10bae44a3f 100644 --- a/workspaces/tech-radar/packages/app-next/src/index.tsx +++ b/workspaces/tech-radar/packages/app-next/src/index.tsx @@ -16,5 +16,6 @@ import '@backstage/cli/asset-types'; import { createRoot } from 'react-dom/client'; import app from './App'; +import '@backstage/ui/css/styles.css'; createRoot(document.getElementById('root')!).render(app); diff --git a/workspaces/tech-radar/packages/app/package.json b/workspaces/tech-radar/packages/app/package.json index 14715eb3a4d..03933dd9784 100644 --- a/workspaces/tech-radar/packages/app/package.json +++ b/workspaces/tech-radar/packages/app/package.json @@ -44,6 +44,7 @@ "@backstage/plugin-techdocs-react": "backstage:^", "@backstage/plugin-user-settings": "backstage:^", "@backstage/theme": "backstage:^", + "@backstage/ui": "backstage:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "react": "^18.0.2", diff --git a/workspaces/tech-radar/packages/app/src/index.tsx b/workspaces/tech-radar/packages/app/src/index.tsx index ea13d30f0dd..95fd8d5212d 100644 --- a/workspaces/tech-radar/packages/app/src/index.tsx +++ b/workspaces/tech-radar/packages/app/src/index.tsx @@ -16,5 +16,6 @@ import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import App from './App'; +import '@backstage/ui/css/styles.css'; ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/workspaces/tech-radar/plugins/tech-radar/README.md b/workspaces/tech-radar/plugins/tech-radar/README.md index aa7d8a1bbd2..562d7064f82 100644 --- a/workspaces/tech-radar/plugins/tech-radar/README.md +++ b/workspaces/tech-radar/plugins/tech-radar/README.md @@ -2,6 +2,8 @@ Screenshot of Tech Radar plugin +> Backstage UI (BUI) is now required for the Tech Radar plugin to function. It has been included as part of Backstage since `1.41.0`, which means you're very likely to already have it installed. The [BUI documentation](https://ui.backstage.io/) has details on installation if needed, and the Backstage [User Interface documentation](https://backstage.io/docs/conf/user-interface/) has details on creating a custom BUI theme. + The Backstage integration for the Tech Radar based on [Zalando's Tech Radar](https://opensource.zalando.com/tech-radar/) open sourced on [GitHub](https://github.com/zalando/tech-radar). This is used at [Spotify](https://spotify.github.io) for visualizing the official guidelines of different areas of software development such as languages, frameworks, infrastructure and processes. Read the [blog post on backstage.io about the Tech Radar](https://backstage.io/blog/2020/05/14/tech-radar-plugin). diff --git a/workspaces/tech-radar/plugins/tech-radar/dev/index.tsx b/workspaces/tech-radar/plugins/tech-radar/dev/index.tsx index 28377753fb4..426823ec43f 100644 --- a/workspaces/tech-radar/plugins/tech-radar/dev/index.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/dev/index.tsx @@ -17,6 +17,9 @@ import { createDevApp } from '@backstage/dev-utils'; import { techRadarPlugin, TechRadarPage } from '../src'; +// eslint-disable-next-line @backstage/no-ui-css-imports-in-non-frontend +import '@backstage/ui/css/styles.css'; + createDevApp() .registerPlugin(techRadarPlugin) .addPage({ diff --git a/workspaces/tech-radar/plugins/tech-radar/package.json b/workspaces/tech-radar/plugins/tech-radar/package.json index fb3d1a6fddd..e88912ac9c1 100644 --- a/workspaces/tech-radar/plugins/tech-radar/package.json +++ b/workspaces/tech-radar/plugins/tech-radar/package.json @@ -60,8 +60,9 @@ "@backstage/core-components": "backstage:^", "@backstage/core-plugin-api": "backstage:^", "@backstage/frontend-plugin-api": "backstage:^", - "@material-ui/core": "^4.12.2", + "@backstage/ui": "backstage:^", "@material-ui/icons": "^4.9.1", + "@remixicon/react": "^4.9.0", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", "color": "^4.0.1", "d3-force": "^3.0.0", diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.module.css new file mode 100644 index 00000000000..998e3e8d3f4 --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.module.css @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .bubble { + pointer-events: none; + user-select: none; + opacity: 0; + } + + .visibleBubble { + pointer-events: none; + user-select: none; + opacity: 0.8; + } + + .background { + fill: #333; + } + + .text { + pointer-events: none; + user-select: none; + font-size: 10px; + fill: #fff; + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx index 4dcb735464b..81182bafb0f 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx @@ -15,7 +15,7 @@ */ import { useRef, useLayoutEffect } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; +import styles from './RadarBubble.module.css'; export type Props = { visible: boolean; @@ -24,30 +24,7 @@ export type Props = { y: number; }; -const useStyles = makeStyles(theme => ({ - bubble: { - pointerEvents: 'none', - userSelect: 'none', - opacity: 0, - }, - visibleBubble: { - pointerEvents: 'none', - userSelect: 'none', - opacity: 0.8, - }, - background: { - fill: '#333', - }, - text: { - pointerEvents: 'none', - userSelect: 'none', - fontSize: '10px', - fill: theme.palette.common.white, - }, -})); - const RadarBubble = (props: Props): React.JSX.Element => { - const classes = useStyles(props); const { visible, text } = props; const textElem = useRef(null); @@ -97,18 +74,14 @@ const RadarBubble = (props: Props): React.JSX.Element => { ref={svgElem} x={0} y={0} - className={visible ? classes.visibleBubble : classes.bubble} + className={visible ? styles.visibleBubble : styles.bubble} data-testid="radar-bubble" > - - + + {text} - + ); }; diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.module.css new file mode 100644 index 00000000000..274342fe4db --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.module.css @@ -0,0 +1,36 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + /* + * Keep the entry dialog opaque in both themes. BUI dark-mode dialog shells can + * be translucent, which made radar labels bleed through the modal content. + */ + .dialog { + background-color: var(--bui-bg-surface-1); + color: var(--bui-fg-primary); + } + + :global([data-theme-mode='dark']) .dialog { + background-color: var(--bui-bg-surface-1); + } + + .dialogLink { + display: inline-flex; + align-items: center; + gap: var(--bui-space-1); + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx index 74aba8fa09e..aacbf544bd5 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -14,16 +14,13 @@ * limitations under the License. */ -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Button from '@material-ui/core/Button'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import LinkIcon from '@material-ui/icons/Link'; +import { Dialog, DialogBody, DialogFooter, DialogHeader } from '@backstage/ui'; +import { RiLinkM } from '@remixicon/react'; import { Link, MarkdownContent } from '@backstage/core-components'; import { isValidUrl } from '../../utils/components'; import type { EntrySnapshot } from '../../utils/types'; import { RadarTimeline } from '../RadarTimeline'; +import styles from './RadarDescription.module.css'; export type Props = { open: boolean; @@ -45,48 +42,51 @@ const RadarDescription = (props: Props): React.JSX.Element => { const { open, onClose, title, description, timeline, url, links } = props; + // Controlled Dialog without DialogTrigger — matches BUI docs and avoids broken + // overlays when opened from SVG legend/blip clicks (no trigger element). return ( { + if (!isOpen) onClose(); + }} > - + {title} - - + + - + {showDialogActions(url, links) && ( - + {links?.map(link => ( - + ))} {isValidUrl(url) && ( - + )} - + )} ); diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.module.css new file mode 100644 index 00000000000..abd4ba0bd7a --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.module.css @@ -0,0 +1,29 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .text { + pointer-events: none; + user-select: none; + font-size: 9px; + fill: #fff; + text-anchor: middle; + } + + .link { + cursor: pointer; + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index b43a8d1b394..f5c314133f3 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -15,10 +15,10 @@ */ import * as React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; import { WithLink } from '../../utils/components'; import { RadarDescription } from '../RadarDescription'; import type { EntrySnapshot } from '../../utils/types'; +import styles from './RadarEntry.module.css'; export type Props = { x: number; @@ -37,20 +37,6 @@ export type Props = { onClick?: (event: React.MouseEvent) => void; }; -const useStyles = makeStyles(theme => ({ - text: { - pointerEvents: 'none', - userSelect: 'none', - fontSize: '9px', - fill: theme.palette.common.white, - textAnchor: 'middle', - }, - - link: { - cursor: 'pointer', - }, -})); - const makeBlip = (color: string, moved?: number) => { const style = { fill: color }; @@ -65,7 +51,6 @@ const makeBlip = (color: string, moved?: number) => { }; const RadarEntry = (props: Props): React.JSX.Element => { - const classes = useStyles(props); const [open, setOpen] = React.useState(false); const { @@ -122,7 +107,7 @@ const RadarEntry = (props: Props): React.JSX.Element => { {description ? ( // eslint-disable-next-line jsx-a11y/anchor-is-valid { {blip} ) : ( - + {blip} )} - + {value} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.module.css new file mode 100644 index 00000000000..ea949e9b44b --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.module.css @@ -0,0 +1,24 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .text { + pointer-events: none; + user-select: none; + font-size: 10px; + fill: var(--bui-fg-secondary); + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx index 6ed80a7e7cd..8ef4faf6965 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; +import styles from './RadarFooter.module.css'; export type Props = { x: number; @@ -25,25 +25,15 @@ export type Props = { const onespace = '\u00a0'; -const useStyles = makeStyles(theme => ({ - text: { - pointerEvents: 'none', - userSelect: 'none', - fontSize: '10px', - fill: theme.palette.text.secondary, - }, -})); - const RadarFooter = (props: Props): React.JSX.Element => { const { x, y, spaces = 5, labels } = props; - const classes = useStyles(props); const space = onespace.repeat(spaces); return ( {`▲ moved up${space}▼ moved down`} {labels && space} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.module.css new file mode 100644 index 00000000000..57dcaef534b --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.module.css @@ -0,0 +1,38 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .ring { + fill: none; + stroke: #bbb; + stroke-width: 1px; + } + + .axis { + fill: none; + stroke: #bbb; + stroke-width: 1px; + } + + .text { + pointer-events: none; + user-select: none; + fill: var(--bui-fg-primary); + font-size: 25px; + font-weight: 800; + opacity: 0.7; + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx index 9d3181cabaa..597cf61e840 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx @@ -14,43 +14,18 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; import type { Ring } from '../../utils/types'; +import styles from './RadarGrid.module.css'; export type Props = { radius: number; rings: Ring[]; }; -const useStyles = makeStyles( - theme => ({ - ring: { - fill: 'none', - stroke: '#bbb', - strokeWidth: '1px', - }, - axis: { - fill: 'none', - stroke: '#bbb', - strokeWidth: '1px', - }, - text: { - pointerEvents: 'none', - userSelect: 'none', - fill: theme.palette.text.primary, - fontSize: '25px', - fontWeight: 800, - opacity: 0.7, - }, - }), - { name: 'PluginTechRadarGrid' }, -); - // A component for the background grid of the radar, with axes, rings etc. It will render around the origin, i.e. // assume that (0, 0) is in the middle of the drawing. const RadarGrid = (props: Props) => { const { radius, rings } = props; - const classes = useStyles(props); const makeRingNode = (ringIndex: number, ringRadius?: number) => [ { cx={0} cy={0} r={ringRadius} - className={classes.ring} + className={styles.ring} />, @@ -80,7 +55,7 @@ const RadarGrid = (props: Props) => { y1={-radius} x2={0} y2={radius} - className={classes.axis} + className={styles.axis} data-testid="radar-grid-x-line" />, // Y axis @@ -90,7 +65,7 @@ const RadarGrid = (props: Props) => { y1={0} x2={radius} y2={0} - className={classes.axis} + className={styles.axis} data-testid="radar-grid-y-line" />, ]; diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.module.css new file mode 100644 index 00000000000..b2649365a19 --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.module.css @@ -0,0 +1,88 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .quadrant { + height: 100%; + width: 100%; + overflow-y: auto; + scrollbar-width: thin; + } + + .quadrantHeading { + pointer-events: none; + user-select: none; + margin-top: 0; + margin-bottom: var(--bui-space-4); + font-size: 18px; + } + + /* columns count is set via inline style */ + .rings { + /* dynamic columns applied inline */ + } + + .ring { + break-inside: avoid-column; + page-break-inside: avoid; + -webkit-column-break-inside: avoid; + font-size: 12px; + margin-bottom: var(--bui-space-4); + } + + .ringEmpty { + color: var(--bui-fg-secondary); + font-size: 12px; + } + + .ringHeading { + pointer-events: none; + user-select: none; + margin-top: 0; + margin-bottom: var(--bui-space-2); + font-size: 12px; + font-weight: 800; + } + + .ringList { + list-style-position: inside; + margin-top: 0; + padding-left: 0; + font-variant-numeric: proportional-nums; + -moz-font-feature-settings: 'pnum'; + -webkit-font-feature-settings: 'pnum'; + font-feature-settings: 'pnum'; + } + + .entry { + pointer-events: visiblePainted; + user-select: none; + font-size: 11px; + } + + .activeEntry { + pointer-events: visiblePainted; + user-select: none; + font-size: 11px; + background: #6f6f6f; + color: #fff; + } + + .entryLink { + pointer-events: visiblePainted; + cursor: pointer; + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 42b8f956bec..1cdf174b964 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -14,84 +14,18 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; import { RadarLegendQuadrant } from './RadarLegendQuadrant'; import { RadarLegendProps } from './types'; import { setupSegments } from './utils'; -const useStyles = makeStyles(theme => ({ - quadrant: { - height: '100%', - width: '100%', - overflowY: 'auto', - scrollbarWidth: 'thin', - }, - quadrantHeading: { - pointerEvents: 'none', - userSelect: 'none', - marginTop: 0, - marginBottom: theme.spacing(2), - fontSize: '18px', - }, - rings: { - columns: (props: { columnCount: number }) => props.columnCount, - }, - ring: { - breakInside: 'avoid-column', - pageBreakInside: 'avoid', - '-webkit-column-break-inside': 'avoid', - fontSize: '12px', - marginBottom: theme.spacing(2), - }, - ringEmpty: { - color: theme.palette.text.secondary, - fontSize: '12px', - }, - ringHeading: { - pointerEvents: 'none', - userSelect: 'none', - marginTop: 0, - marginBottom: theme.spacing(1), - fontSize: '12px', - fontWeight: 800, - }, - ringList: { - listStylePosition: 'inside', - marginTop: 0, - paddingLeft: 0, - fontVariantNumeric: 'proportional-nums', - '-moz-font-feature-settings': 'pnum', - '-webkit-font-feature-settings': 'pnum', - 'font-feature-settings': 'pnum', - }, - entry: { - pointerEvents: 'visiblePainted', - userSelect: 'none', - fontSize: '11px', - }, - activeEntry: { - pointerEvents: 'visiblePainted', - userSelect: 'none', - fontSize: '11px', - background: '#6f6f6f', - color: theme.palette.common.white, - }, - entryLink: { - pointerEvents: 'visiblePainted', - cursor: 'pointer', - }, -})); - const RadarLegend = ({ quadrants, rings, entries, + columnCount, onEntryMouseEnter, onEntryMouseLeave, - ...props }: RadarLegendProps): React.JSX.Element => { - const classes = useStyles(props); - return ( {quadrants.map(quadrant => ( @@ -100,7 +34,7 @@ const RadarLegend = ({ segments={setupSegments(entries)} quadrant={quadrant} rings={rings} - classes={classes} + columnCount={columnCount} onEntryMouseEnter={onEntryMouseEnter} onEntryMouseLeave={onEntryMouseLeave} /> diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx index c22bb18d542..c5794290626 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx @@ -13,19 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClassNameMap } from '@material-ui/core/styles/withStyles'; -import Typography from '@material-ui/core/Typography'; import { useState } from 'react'; import { WithLink } from '../../utils/components'; import { RadarDescription } from '../RadarDescription'; import type { EntrySnapshot } from '../../utils/types'; +import styles from './RadarLegend.module.css'; type RadarLegendLinkProps = { entryId: string; url?: string; description?: string; title?: string; - classes: ClassNameMap; active?: boolean; links: Array<{ url: string; title: string }>; timeline: EntrySnapshot[]; @@ -36,7 +34,6 @@ export const RadarLegendLink = ({ url, description, title, - classes, active, links, timeline, @@ -58,22 +55,20 @@ export const RadarLegendLink = ({ if (description) { return ( <> - - {title} - - + + {open && ( - + + {title} - + ); }; diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx index 04c128b4648..269bf97036d 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ -import { ClassNameMap } from '@material-ui/core/styles/withStyles'; import { Quadrant, Ring } from '../../utils/types'; import { RadarLegendRing } from './RadarLegendRing'; import { RadarLegendProps, Segments } from './types'; import { getSegment } from './utils'; +import styles from './RadarLegend.module.css'; type RadarLegendQuadrantProps = { segments: Segments; quadrant: Quadrant; rings: Ring[]; - classes: ClassNameMap; + columnCount: number; onEntryMouseEnter: RadarLegendProps['onEntryMouseEnter']; onEntryMouseLeave: RadarLegendProps['onEntryMouseLeave']; }; @@ -33,7 +33,7 @@ export const RadarLegendQuadrant = ({ segments, quadrant, rings, - classes, + columnCount, onEntryMouseEnter, onEntryMouseLeave, }: RadarLegendQuadrantProps) => { @@ -46,14 +46,13 @@ export const RadarLegendQuadrant = ({ height={quadrant.legendHeight} data-testid="radar-quadrant" > -
-

{quadrant.name}

-
+
+

{quadrant.name}

+
{rings.map(ring => ( ; onEntryMouseEnter?: RadarLegendProps['onEntryMouseEnter']; onEntryMouseLeave?: RadarLegendProps['onEntryMouseEnter']; }; @@ -30,25 +28,22 @@ type RadarLegendRingProps = { export const RadarLegendRing = ({ ring, entries, - classes, onEntryMouseEnter, onEntryMouseLeave, }: RadarLegendRingProps) => { return ( -
+

{ring.name}

{entries.length === 0 ? ( - - (empty) - +

(empty)

) : ( -
    +
      {entries.map(entry => (
    1. ({ - overflowXScroll: { - overflowX: 'scroll', - }, -})); +import styles from './RadarPage.module.css'; /** * Properties for {@link TechRadarPage} @@ -67,22 +59,29 @@ export function RadarPage(props: TechRadarPageProps) { pageTitle = 'Company Radar', ...componentProps } = props; - const classes = useStyles(); const [searchText, setSearchText] = useState(''); return (
      - + - setSearchText(e.target.value)} - /> + + setSearchText(value)} + style={{ width: '100%', minWidth: 200 }} + /> + - + This is used for visualizing the official guidelines of different areas of software development such as languages, frameworks, infrastructure and processes. You can find an explanation for the @@ -91,14 +90,16 @@ export function RadarPage(props: TechRadarPageProps) { Zalando Tech Radar . - + - - - - - + {/* + Full-width radar (demo parity). The previous MUI Grid sm=6/md=4 column + left a large empty region and made the filter feel "detached". + */} +
      + +
      ); diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.module.css b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.module.css new file mode 100644 index 00000000000..29b047bf009 --- /dev/null +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.module.css @@ -0,0 +1,53 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer components { + .tableContainer { + width: 100%; + overflow-x: auto; + margin-top: var(--bui-space-4); + border-radius: var(--bui-radius-2); + border: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + } + + .table { + width: 100%; + border-collapse: collapse; + table-layout: auto; + font-size: var(--bui-font-size-2); + background-color: var(--bui-bg-surface-1); + color: var(--bui-fg-primary); + } + + .th { + padding: var(--bui-space-2) var(--bui-space-4); + text-align: left; + font-weight: var(--bui-font-weight-bold); + border-bottom: 1px solid var(--bui-border); + color: var(--bui-fg-primary); + background-color: var(--bui-bg-surface-1); + white-space: nowrap; + } + + .td { + padding: var(--bui-space-2) var(--bui-space-4); + border-bottom: 1px solid var(--bui-border); + color: var(--bui-fg-primary); + vertical-align: top; + background-color: var(--bui-bg-surface-1); + } +} diff --git a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.tsx b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.tsx index 238946bfaff..6da4455ff26 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.tsx +++ b/workspaces/tech-radar/plugins/tech-radar/src/components/RadarTimeline/RadarTimeline.tsx @@ -15,21 +15,15 @@ */ import type { EntrySnapshot } from '../../utils/types'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; -import Paper from '@material-ui/core/Paper'; -import Typography from '@material-ui/core/Typography'; - -import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; -import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; -import AdjustIcon from '@material-ui/icons/Adjust'; - +import { Text } from '@backstage/ui'; +import { + RiArrowUpLine, + RiArrowDownLine, + RiRadioButtonLine, +} from '@remixicon/react'; import { MarkdownContent } from '@backstage/core-components'; import { MovedState } from '@backstage-community/plugin-tech-radar-common'; +import styles from './RadarTimeline.module.css'; export type Props = { timeline?: EntrySnapshot[]; @@ -40,53 +34,47 @@ const RadarTimeline = (props: Props): React.JSX.Element => { return ( <> - - History - - - - - - + History +
      +
      + + + + + + + + + {timeline?.length === 0 && ( - - - No Timeline - - + + + )} {timeline?.map(timeEntry => ( - - - {timeEntry.moved === MovedState.Up ? : ''} - {timeEntry.moved === MovedState.Down ? ( - - ) : ( - '' + + + + + + ))} - -
      Moved in direction - - Moved to ring - Moved on date - Description - - - + Moved to ringMoved on dateDescription
      No Timeline
      + {timeEntry.moved === MovedState.Up && ( + )} - {timeEntry.moved === MovedState.NoChange ? ( - - ) : ( - '' + {timeEntry.moved === MovedState.Down && ( + + )} + {timeEntry.moved === MovedState.NoChange && ( + )} - - + {timeEntry.ring.name ? timeEntry.ring.name : ''} - - + {timeEntry.date.toLocaleDateString() ? timeEntry.date.toLocaleDateString() : ''} - - + {timeEntry.description ? ( { ) : ( '' )} - - +
      -
      + + +
); }; diff --git a/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts b/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts index af3f97d7caa..71cb5a23c01 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts +++ b/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts @@ -252,21 +252,29 @@ export class DefaultTechRadarApi implements TechRadarApi { } async load() { - const { token: idToken } = await this.identityApi.getCredentials(); - const apiUrl = await this.discoveryApi.getBaseUrl('tech-radar'); - const response = await this.fetchApi.fetch(`${apiUrl}/data`, { - headers: { - 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - }); - if (response.status === 200) { - const respJson = await response.json(); - const validationResult = - TechRadarLoaderResponseParser.safeParse(respJson); - if (validationResult.success) { - return validationResult.data; + try { + const { token: idToken } = await this.identityApi.getCredentials(); + const apiUrl = await this.discoveryApi.getBaseUrl('tech-radar'); + const response = await this.fetchApi.fetch(`${apiUrl}/data`, { + headers: { + 'Content-Type': 'application/json', + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }); + if (response.status === 200) { + const respJson = await response.json(); + const validationResult = + TechRadarLoaderResponseParser.safeParse(respJson); + if (validationResult.success) { + return validationResult.data; + } } + } catch (e) { + // eslint-disable-next-line no-console + console.warn( + 'tech-radar: backend unavailable, falling back to mock data', + e, + ); } return mock; diff --git a/workspaces/tech-radar/yarn.lock b/workspaces/tech-radar/yarn.lock index 9b148d3d1d5..93e3166a857 100644 --- a/workspaces/tech-radar/yarn.lock +++ b/workspaces/tech-radar/yarn.lock @@ -2153,8 +2153,9 @@ __metadata: "@backstage/frontend-plugin-api": "backstage:^" "@backstage/frontend-test-utils": "backstage:^" "@backstage/test-utils": "backstage:^" - "@material-ui/core": "npm:^4.12.2" + "@backstage/ui": "backstage:^" "@material-ui/icons": "npm:^4.9.1" + "@remixicon/react": "npm:^4.9.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^15.0.0" @@ -4591,7 +4592,7 @@ __metadata: languageName: node linkType: hard -"@backstage/ui@npm:^0.17.0": +"@backstage/ui@backstage:^::backstage=1.53.0&npm=0.17.0, @backstage/ui@npm:^0.17.0": version: 0.17.0 resolution: "@backstage/ui@npm:0.17.0" dependencies: @@ -8973,6 +8974,15 @@ __metadata: languageName: node linkType: hard +"@remixicon/react@npm:^4.9.0": + version: 4.9.0 + resolution: "@remixicon/react@npm:4.9.0" + peerDependencies: + react: ">=18.2.0" + checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295 + languageName: node + linkType: hard + "@rjsf/core@npm:5.24.13": version: 5.24.13 resolution: "@rjsf/core@npm:5.24.13" @@ -13803,6 +13813,7 @@ __metadata: "@backstage/plugin-user-settings": "backstage:^" "@backstage/test-utils": "backstage:^" "@backstage/theme": "backstage:^" + "@backstage/ui": "backstage:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@playwright/test": "npm:^1.32.3" @@ -13851,6 +13862,7 @@ __metadata: "@backstage/plugin-techdocs-react": "backstage:^" "@backstage/plugin-user-settings": "backstage:^" "@backstage/theme": "backstage:^" + "@backstage/ui": "backstage:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@playwright/test": "npm:^1.32.3" From 91b9be99d66da6aabb5e5db6c1e2658dfedc4ada Mon Sep 17 00:00:00 2001 From: jaisuriyaNatarajan Date: Tue, 11 Aug 2026 16:33:58 +0000 Subject: [PATCH 2/3] fix(tech-radar): revert unrelated DefaultTechRadarApi try/catch Signed-off-by: jaisuriyaNatarajan Co-authored-by: Cursor --- .../plugins/tech-radar/src/defaultApi.ts | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts b/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts index 71cb5a23c01..af3f97d7caa 100644 --- a/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts +++ b/workspaces/tech-radar/plugins/tech-radar/src/defaultApi.ts @@ -252,29 +252,21 @@ export class DefaultTechRadarApi implements TechRadarApi { } async load() { - try { - const { token: idToken } = await this.identityApi.getCredentials(); - const apiUrl = await this.discoveryApi.getBaseUrl('tech-radar'); - const response = await this.fetchApi.fetch(`${apiUrl}/data`, { - headers: { - 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - }); - if (response.status === 200) { - const respJson = await response.json(); - const validationResult = - TechRadarLoaderResponseParser.safeParse(respJson); - if (validationResult.success) { - return validationResult.data; - } + const { token: idToken } = await this.identityApi.getCredentials(); + const apiUrl = await this.discoveryApi.getBaseUrl('tech-radar'); + const response = await this.fetchApi.fetch(`${apiUrl}/data`, { + headers: { + 'Content-Type': 'application/json', + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }); + if (response.status === 200) { + const respJson = await response.json(); + const validationResult = + TechRadarLoaderResponseParser.safeParse(respJson); + if (validationResult.success) { + return validationResult.data; } - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - 'tech-radar: backend unavailable, falling back to mock data', - e, - ); } return mock; From 85eaa8467ab10ed4a5f0afa80bd167c34acc97d9 Mon Sep 17 00:00:00 2001 From: jaisuriyaNatarajan Date: Wed, 12 Aug 2026 06:58:11 +0000 Subject: [PATCH 3/3] chore(tech-radar): dedupe yarn.lock Signed-off-by: jaisuriyaNatarajan --- workspaces/tech-radar/yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/tech-radar/yarn.lock b/workspaces/tech-radar/yarn.lock index 93e3166a857..457ca3414b5 100644 --- a/workspaces/tech-radar/yarn.lock +++ b/workspaces/tech-radar/yarn.lock @@ -8965,7 +8965,7 @@ __metadata: languageName: node linkType: hard -"@remixicon/react@npm:>=4.6.0 <4.9.0, @remixicon/react@npm:^4.6.0": +"@remixicon/react@npm:>=4.6.0 <4.9.0": version: 4.8.0 resolution: "@remixicon/react@npm:4.8.0" peerDependencies: @@ -8974,7 +8974,7 @@ __metadata: languageName: node linkType: hard -"@remixicon/react@npm:^4.9.0": +"@remixicon/react@npm:^4.6.0, @remixicon/react@npm:^4.9.0": version: 4.9.0 resolution: "@remixicon/react@npm:4.9.0" peerDependencies: