]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19068 - UI glitch when announcement message is set
authorKevin Silva <kevin.silva@sonarsource.com>
Fri, 28 Jul 2023 08:42:59 +0000 (10:42 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 28 Jul 2023 20:03:15 +0000 (20:03 +0000)
server/sonar-web/design-system/src/components/Banner.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/MainAppBar.tsx
server/sonar-web/design-system/src/components/__tests__/Banner-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx

diff --git a/server/sonar-web/design-system/src/components/Banner.tsx b/server/sonar-web/design-system/src/components/Banner.tsx
new file mode 100644 (file)
index 0000000..90a6cf7
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { ReactNode } from 'react';
+import tw from 'twin.macro';
+import {
+  LAYOUT_BANNER_HEIGHT,
+  LAYOUT_VIEWPORT_MIN_WIDTH,
+  themeColor,
+  themeContrast,
+} from '../helpers';
+import { translate } from '../helpers/l10n';
+import { ThemeColors } from '../types';
+import { InteractiveIconBase } from './InteractiveIcon';
+import { CloseIcon, FlagErrorIcon, FlagInfoIcon, FlagSuccessIcon, FlagWarningIcon } from './icons';
+
+export type Variant = 'error' | 'warning' | 'success' | 'info';
+
+interface Props {
+  children: ReactNode;
+  onDismiss?: VoidFunction;
+  variant: Variant;
+}
+
+function getVariantInfo(variant: Variant) {
+  const variantList = {
+    error: {
+      icon: <FlagErrorIcon />,
+      fontColor: 'errorText',
+      backGroundColor: 'errorBackground',
+    },
+    warning: {
+      icon: <FlagWarningIcon />,
+      fontColor: 'warningText',
+      backGroundColor: 'warningBackground',
+    },
+    success: {
+      icon: <FlagSuccessIcon />,
+      fontColor: 'successText',
+      backGroundColor: 'successBackground',
+    },
+    info: {
+      icon: <FlagInfoIcon />,
+      fontColor: 'infoText',
+      backGroundColor: 'infoBackground',
+    },
+  } as const;
+
+  return variantList[variant];
+}
+
+export function Banner({ children, onDismiss, variant }: Props) {
+  const variantInfo = getVariantInfo(variant);
+
+  return (
+    <div role="alert" style={{ height: LAYOUT_BANNER_HEIGHT }}>
+      <BannerWrapper
+        backGroundColor={variantInfo.backGroundColor}
+        fontColor={variantInfo.fontColor}
+      >
+        <BannerInner>
+          {variantInfo.icon}
+          {children}
+          {onDismiss && (
+            <BannerCloseIcon
+              Icon={CloseIcon}
+              aria-label={translate('dismiss')}
+              onClick={onDismiss}
+              size="small"
+            />
+          )}
+        </BannerInner>
+      </BannerWrapper>
+    </div>
+  );
+}
+
+const BannerWrapper = styled.div<{
+  backGroundColor: ThemeColors;
+  fontColor: ThemeColors;
+}>`
+  min-width: ${LAYOUT_VIEWPORT_MIN_WIDTH}px;
+  max-width: 100%;
+  height: inherit;
+  background-color: ${({ backGroundColor }) => themeColor(backGroundColor)};
+  color: ${({ fontColor }) => themeColor(fontColor)};
+  ${tw`sw-z-global-navbar sw-fixed sw-w-full`}
+  ${tw`sw-sticky sw-top-0`}
+`;
+
+const BannerInner = styled.div`
+  width: 100%;
+  height: inherit;
+
+  ${tw`sw-box-border`}
+  ${tw`sw-flex sw-items-center sw-gap-3`}
+  ${tw`sw-px-4`}
+  ${tw`sw-body-sm`}
+`;
+
+const BannerCloseIcon = styled(InteractiveIconBase)`
+  --background: ${themeColor('bannerIcon')};
+  --backgroundHover: ${themeColor('bannerIconHover')};
+  --color: ${themeContrast('bannerIcon')};
+  --colorHover: ${themeContrast('bannerIconHover')};
+  --focus: ${themeColor('bannerIconFocus', 0.2)};
+`;
index 0712a133143925d19c97420738f9e5f692b7111b..8a770e6134f3ab2571d6ccf10a779f3466f93095 100644 (file)
@@ -31,13 +31,11 @@ import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
 import { BaseLink } from './Link';
 
 const MainAppBarDiv = styled.div`
-  ${tw`sw-sticky sw-top-0`}
   ${tw`sw-flex`};
   ${tw`sw-items-center`};
   ${tw`sw-px-6`};
   ${tw`sw-w-full`};
   ${tw`sw-box-border`};
-  ${tw`sw-z-global-navbar`};
 
   background: ${themeColor('mainBar')};
   border-bottom: ${themeBorder('default')};
diff --git a/server/sonar-web/design-system/src/components/__tests__/Banner-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Banner-test.tsx
new file mode 100644 (file)
index 0000000..f07e4d6
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+import { Banner } from '../Banner';
+import { Note } from '../Text';
+
+it('should render with close button', async () => {
+  const onDismiss = jest.fn();
+  const { user } = setupWithProps({ onDismiss });
+  expect(
+    screen.getByRole('button', {
+      name: 'dismiss',
+    })
+  ).toBeVisible();
+
+  await user.click(
+    screen.getByRole('button', {
+      name: 'dismiss',
+    })
+  );
+
+  expect(onDismiss).toHaveBeenCalledTimes(1);
+});
+
+function setupWithProps(props: Partial<FCProps<typeof Banner>> = {}) {
+  return render(
+    <Banner {...props} variant="warning">
+      <Note className="sw-body-sm">{props.children ?? 'Test Message'}</Note>
+    </Banner>
+  );
+}
index 7ae0b61692b0c6d772a462e79d2baf482e5073a5..0e4783df60773a472b8dfeed2f67a183d8579731 100644 (file)
@@ -21,6 +21,7 @@
 export * from './Accordion';
 export * from './Avatar';
 export { Badge } from './Badge';
+export * from './Banner';
 export { BarChart } from './BarChart';
 export { Breadcrumbs } from './Breadcrumbs';
 export * from './BubbleChart';
index c2d3738838d68e07608dd4bf6e3ea55ceee92b59..b303f3f865502da0399818244cde6feedff488c5 100644 (file)
@@ -163,12 +163,15 @@ export const lightTheme = {
 
     warningBorder: COLORS.yellow[400],
     warningBackground: COLORS.yellow[50],
+    warningText: COLORS.yellow[900],
 
     successBorder: COLORS.green[400],
     successBackground: COLORS.green[50],
+    successText: COLORS.green[900],
 
     infoBorder: COLORS.blue[400],
     infoBackground: COLORS.blue[50],
+    infoText: COLORS.blue[900],
 
     // banner message
     bannerMessage: danger.lightest,
index 2d99e1311e48496e55b271300acbc9c4e5b1acef..f4924fa10b7b9186c30eafe5cdb4219dc4f3a6de 100644 (file)
@@ -76,10 +76,12 @@ export default function GlobalContainer() {
                     <IndexationContextProvider>
                       <LanguagesContextProvider>
                         <MetricsContextProvider>
-                          <SystemAnnouncement />
-                          <IndexationNotification />
-                          <UpdateNotification dismissable />
-                          <GlobalNav location={location} />
+                          <div className="sw-sticky sw-top-0 sw-z-global-navbar">
+                            <SystemAnnouncement />
+                            <IndexationNotification />
+                            <UpdateNotification dismissable />
+                            <GlobalNav location={location} />
+                          </div>
                           <Outlet />
                         </MetricsContextProvider>
                       </LanguagesContextProvider>