Skip to content

Commit a598f92

Browse files
authored
announcements(refactor): Migrate banner to BUI alert (#7422)
* refactor: Migrate banner to BUI alert Signed-off-by: gaelgoth <gothuey.gael@gmail.com> * refactor: Move CSS outside compononent and use Box instead of empty tag Signed-off-by: gaelgoth <gothuey.gael@gmail.com> --------- Signed-off-by: gaelgoth <gothuey.gael@gmail.com>
1 parent b5c6a25 commit a598f92

9 files changed

Lines changed: 77 additions & 318 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@backstage-community/plugin-announcements': minor
3+
---
4+
5+
Migrate Announcements Banner to the Backstage UI using the Alert component.
6+
7+
This change also removes the `variant` React prop. If you were using the prop with the `block` or `floating` values, it can be safely removed, as the banner now uses the Backstage UI Alert default style.

workspaces/announcements/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"@changesets/cli": "^2.27.1",
4848
"@playwright/test": "^1.32.3",
4949
"knip": "^5.27.4",
50-
"node-gyp": "^10.0.0",
5150
"prettier": "^2.3.2",
5251
"typescript": "~5.4.0"
5352
},

workspaces/announcements/plugins/announcements/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ app:
9696
extensions:
9797
- announcements/banner:
9898
config:
99-
variant: floating
10099
max: 2
101100
category: updates
102101
active: true

workspaces/announcements/plugins/announcements/report-alpha.api.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,13 @@ const _default: OverridableFrontendPlugin<
5252
}>;
5353
'app-root-element:announcements/banner': OverridableExtensionDefinition<{
5454
config: {
55-
variant: 'block' | 'floating';
56-
max: number | undefined;
55+
max: number;
5756
category: string | undefined;
5857
active: boolean | undefined;
5958
current: boolean | undefined;
6059
tags: string[] | undefined;
6160
};
6261
configInput: {
63-
variant?: 'block' | 'floating' | undefined;
6462
max?: number | undefined;
6563
active?: boolean | undefined;
6664
current?: boolean | undefined;

workspaces/announcements/plugins/announcements/report.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ export type MarkdownRendererTypeProps = 'backstage' | 'md-editor';
6363

6464
// @public (undocumented)
6565
export const NewAnnouncementBanner: (props: {
66-
variant?: 'block' | 'floating' | undefined;
6766
max?: number | undefined;
6867
category?: string | undefined;
6968
active?: boolean | undefined;

workspaces/announcements/plugins/announcements/src/alpha/banner.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ export const announcementsBanner = AppRootElementBlueprint.makeWithOverrides({
2525
name: 'banner',
2626
config: {
2727
schema: {
28-
variant: z => z.enum(['block', 'floating']).default('floating'),
29-
max: z => z.number().optional(),
28+
max: z => z.number().optional().default(1),
3029
category: z => z.string().optional(),
3130
active: z => z.boolean().optional(),
3231
current: z => z.boolean().optional(),

workspaces/announcements/plugins/announcements/src/components/NewAnnouncementBanner/NewAnnouncementBanner.test.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ describe('NewAnnouncementBanner', () => {
8585

8686
await renderNewAnnouncementBanner(mockAnnouncementsApi(announcementsList));
8787

88-
expect(await screen.findByText('New Announcement')).toBeInTheDocument();
88+
const titles = await screen.findAllByText(/New Announcement/i);
89+
expect(titles.length).toBeGreaterThan(0);
8990
expect(screen.getByText(/This is a new announcement/i)).toBeInTheDocument();
91+
expect(screen.getAllByRole('link').length).toBeGreaterThan(0);
9092
});
9193

9294
it('hides the announcement banner when dismissed', async () => {
@@ -110,11 +112,13 @@ describe('NewAnnouncementBanner', () => {
110112

111113
await renderNewAnnouncementBanner(mockApi);
112114

113-
const dismissButton = await screen.findByTitle(/mark as seen/i);
115+
const dismissButton = await screen.findByRole('button', {
116+
name: /mark as seen/i,
117+
});
114118
fireEvent.click(dismissButton);
115119

116120
await waitFor(() => {
117-
expect(screen.queryByText('New Announcement')).not.toBeInTheDocument();
121+
expect(screen.queryAllByText('New Announcement').length).toBe(0);
118122
});
119123

120124
expect(mockApi.markLastSeenDate).toHaveBeenCalled();
@@ -128,7 +132,7 @@ describe('NewAnnouncementBanner', () => {
128132

129133
await renderNewAnnouncementBanner(mockApi);
130134

131-
expect(screen.queryByText(/new announcement/i)).not.toBeInTheDocument();
135+
expect(screen.queryAllByText(/new announcement/i).length).toBe(0);
132136
});
133137

134138
it('captures analytics view events when banner displays', async () => {
@@ -197,7 +201,8 @@ describe('NewAnnouncementBanner', () => {
197201
analyticsApi,
198202
);
199203

200-
const link = await screen.findByText('Clickable Banner Announcement');
204+
const links = await screen.findAllByRole('link');
205+
const link = links[0];
201206
fireEvent.click(link);
202207

203208
await waitFor(() => {

workspaces/announcements/plugins/announcements/src/components/NewAnnouncementBanner/NewAnnouncementBanner.tsx

Lines changed: 50 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 The Backstage Authors
2+
* Copyright 2026 The Backstage Authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
1515
*/
1616
import { useEffect, useState } from 'react';
1717
import { DateTime } from 'luxon';
18-
import { Link } from '@backstage/core-components';
18+
import { Link, Alert, ButtonIcon, Box } from '@backstage/ui';
1919
import { useApi, useRouteRef, useAnalytics } from '@backstage/core-plugin-api';
2020
import {
2121
announcementsApiRef,
@@ -31,71 +31,44 @@ import {
3131
} from '@backstage-community/plugin-announcements-common';
3232
import { useSignal } from '@backstage/plugin-signals-react';
3333
import {
34-
makeStyles,
35-
Snackbar,
36-
SnackbarContent,
37-
IconButton,
38-
Typography,
39-
} from '@material-ui/core';
40-
import Close from '@material-ui/icons/Close';
41-
import { Alert } from '@material-ui/lab';
34+
RiCloseLine,
35+
RiMegaphoneLine,
36+
RiExternalLinkLine,
37+
} from '@remixicon/react';
4238

4339
import { announcementViewRouteRef } from '../../routes';
4440
import { truncate } from '../utils/truncateUtils';
4541

46-
const useStyles = makeStyles(theme => {
47-
return {
48-
// showing on top, as a block
49-
blockPositioning: {
50-
padding: theme?.spacing?.(0) ?? 0,
51-
position: 'relative',
52-
marginBottom: theme?.spacing?.(4) ?? 32,
53-
marginTop: theme?.spacing?.(3) ?? -24,
54-
zIndex: 'unset',
55-
},
56-
// showing on top, as a floating alert
57-
floatingPositioning: {},
58-
icon: {
59-
fontSize: 20,
60-
},
61-
bannerIcon: {
62-
fontSize: 20,
63-
marginRight: '0.5rem',
64-
},
65-
content: {
66-
width: '100%',
67-
maxWidth: 'inherit',
68-
flexWrap: 'nowrap',
69-
backgroundColor: theme?.palette?.banner?.info ?? '#f0f0f0',
70-
display: 'flex',
71-
alignItems: 'center',
72-
color: theme?.palette?.banner?.text ?? '#000000',
73-
'& a': {
74-
color: theme?.palette?.banner?.link ?? '#0068c8',
75-
},
76-
},
77-
};
78-
});
79-
8042
type CardOptions = {
8143
titleLength?: number;
8244
excerptLength?: number;
8345
};
8446

8547
type AnnouncementBannerProps = {
8648
announcement: Announcement;
87-
variant?: 'block' | 'floating';
8849
cardOptions?: CardOptions;
8950
};
9051

52+
const floatingStyle: React.CSSProperties = {
53+
position: 'fixed',
54+
top: 20,
55+
left: '50%',
56+
transform: 'translateX(-50%)',
57+
};
58+
59+
const externalLinkIconStyle: React.CSSProperties = {
60+
width: '0.85em',
61+
height: '0.85em',
62+
verticalAlign: 'middle',
63+
marginLeft: 5,
64+
};
65+
9166
const AnnouncementBanner = (props: AnnouncementBannerProps) => {
92-
const classes = useStyles();
9367
const announcementsApi = useApi(announcementsApiRef);
9468
const viewAnnouncementLink = useRouteRef(announcementViewRouteRef);
9569
const analytics = useAnalytics();
9670
const { t } = useAnnouncementsTranslation();
9771
const [bannerOpen, setBannerOpen] = useState(true);
98-
const variant = props.variant || 'block';
9972
const announcement = props.announcement;
10073
const titleLength = props.cardOptions?.titleLength;
10174
const excerptLength = props.cardOptions?.excerptLength;
@@ -136,24 +109,26 @@ const AnnouncementBanner = (props: AnnouncementBannerProps) => {
136109
? truncate(announcement.excerpt, excerptLength)
137110
: announcement.excerpt;
138111

139-
const message = (
140-
<>
141-
<Typography
142-
component="span"
143-
className={classes.bannerIcon}
144-
variant="inherit"
145-
>
146-
📣
147-
</Typography>
112+
const bannerTitle = (
113+
<Box>
114+
{title}
148115
<Link
149-
to={viewAnnouncementLink({ id: announcement.id })}
150-
variant="inherit"
116+
href={viewAnnouncementLink({ id: announcement.id })}
151117
onClick={handleLinkClick}
118+
variant="body-large"
152119
>
153-
{title}
120+
<RiExternalLinkLine aria-hidden style={externalLinkIconStyle} />
154121
</Link>
155-
&nbsp;– {excerpt}
156-
</>
122+
</Box>
123+
);
124+
125+
const closeButton = (
126+
<ButtonIcon
127+
icon={<RiCloseLine />}
128+
aria-label={t('newAnnouncementBanner.markAsSeen')}
129+
onPress={handleDismiss}
130+
variant="tertiary"
131+
/>
157132
);
158133

159134
useEffect(() => {
@@ -169,36 +144,24 @@ const AnnouncementBanner = (props: AnnouncementBannerProps) => {
169144
});
170145
}, [analytics, announcement.id, announcement.title, bannerOpen]);
171146

147+
if (!bannerOpen) {
148+
return null;
149+
}
150+
172151
return (
173-
<Snackbar
174-
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
175-
open={bannerOpen}
176-
className={
177-
variant === 'block'
178-
? classes.blockPositioning
179-
: classes.floatingPositioning
180-
}
181-
>
182-
<SnackbarContent
183-
className={classes.content}
184-
message={message}
185-
action={[
186-
<IconButton
187-
key="dismiss"
188-
title={t('newAnnouncementBanner.markAsSeen')}
189-
color="inherit"
190-
onClick={handleDismiss}
191-
>
192-
<Close className={classes.icon} />
193-
</IconButton>,
194-
]}
195-
/>
196-
</Snackbar>
152+
<Alert
153+
style={floatingStyle}
154+
status="info"
155+
icon={<RiMegaphoneLine />}
156+
title={bannerTitle}
157+
description={excerpt}
158+
customActions={closeButton}
159+
mb="4"
160+
/>
197161
);
198162
};
199163

200164
type NewAnnouncementBannerProps = {
201-
variant?: 'block' | 'floating';
202165
max?: number;
203166
category?: string;
204167
active?: boolean;
@@ -214,7 +177,6 @@ export const NewAnnouncementBanner = (props: NewAnnouncementBannerProps) => {
214177
category,
215178
tags,
216179
active,
217-
variant,
218180
current,
219181
sortBy,
220182
cardOptions = {
@@ -254,7 +216,7 @@ export const NewAnnouncementBanner = (props: NewAnnouncementBannerProps) => {
254216
if (loading) {
255217
return null;
256218
} else if (error) {
257-
return <Alert severity="error">{error.message}</Alert>;
219+
return <Alert status="danger" title={error.message} />;
258220
}
259221

260222
if (announcements.count === 0) {
@@ -279,7 +241,6 @@ export const NewAnnouncementBanner = (props: NewAnnouncementBannerProps) => {
279241
<AnnouncementBanner
280242
key={announcement.id}
281243
announcement={announcement}
282-
variant={variant}
283244
cardOptions={cardOptions}
284245
/>
285246
))}

0 commit comments

Comments
 (0)