feat(core-components): Avoid Layout Shift for DismissableBanner when using a storageApi with latency (e.g. user-settings-backend)

Properly handle the `unknown` state of the storageApi. This may lead to some Layout Shift if the banner has not been dismissed, but once it has been dismissed, you won't have any.

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>
This commit is contained in:
Gabriel Dugny
2025-03-03 16:23:41 +01:00
parent 753c1a1c78
commit 0a0ced65da
2 changed files with 25 additions and 5 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/core-components': patch
---
Avoid Layout Shift for DismissableBanner when using a storageApi with latency (e.g. user-settings-backend)
Properly handle the `unknown` state of the storageApi. There's a trade-off: this may lead to some Layout Shift if the banner has not been dismissed, but once it has been dismissed, you won't have any.
@@ -102,11 +102,14 @@ export const DismissableBanner = (props: Props) => {
const classes = useStyles();
const storageApi = useApi(storageApiRef);
const notificationsStore = storageApi.forBucket('notifications');
const rawDismissedBanners =
notificationsStore.snapshot<string[]>('dismissedBanners').value ?? [];
const dismissedBannersSnapshot =
notificationsStore.snapshot<string[]>('dismissedBanners');
const [dismissedBanners, setDismissedBanners] = useState(
new Set(rawDismissedBanners),
new Set(dismissedBannersSnapshot.value ?? []),
);
const [loadingSettings, setLoadingSettings] = useState(
dismissedBannersSnapshot.presence === 'unknown',
);
const observedItems = useObservable(
@@ -114,11 +117,21 @@ export const DismissableBanner = (props: Props) => {
);
useEffect(() => {
if (observedItems?.presence === 'unknown' || observedItems === undefined) {
setLoadingSettings(true);
}
if (observedItems?.value) {
const currentValue = observedItems?.value ?? [];
setDismissedBanners(new Set(currentValue));
}
}, [observedItems?.value]);
if (
observedItems?.presence === 'absent' ||
observedItems?.presence === 'present'
) {
setLoadingSettings(false);
}
}, [observedItems]);
const handleClick = () => {
notificationsStore.set('dismissedBanners', [...dismissedBanners, id]);
@@ -131,7 +144,7 @@ export const DismissableBanner = (props: Props) => {
? { vertical: 'bottom', horizontal: 'center' }
: { vertical: 'top', horizontal: 'center' }
}
open={!dismissedBanners.has(id)}
open={!loadingSettings && !dismissedBanners.has(id)}
classes={{
root: classNames(classes.root, !fixed && classes.topPosition),
}}