aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/design-system/src/components/QualityGateIndicator.tsx')
-rw-r--r--server/sonar-web/design-system/src/components/QualityGateIndicator.tsx182
1 files changed, 182 insertions, 0 deletions
diff --git a/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx b/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx
new file mode 100644
index 00000000000..5c8ac70cc71
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx
@@ -0,0 +1,182 @@
+/*
+ * 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 { useTheme } from '@emotion/react';
+import React from 'react';
+import { theme as twTheme } from 'twin.macro';
+import { BasePlacement, PopupPlacement } from '../helpers/positioning';
+import { themeColor, themeContrast } from '../helpers/theme';
+
+const SIZE = {
+ sm: twTheme('spacing.4'),
+ md: twTheme('spacing.6'),
+ xl: twTheme('spacing.16'),
+};
+
+type QGStatus = 'ERROR' | 'OK' | 'NONE' | 'NOT_COMPUTED';
+
+interface Props {
+ ariaLabel?: string;
+ className?: string;
+ size?: keyof typeof SIZE;
+ status: QGStatus;
+ tooltipPlacement?: BasePlacement;
+ withTooltip?: boolean;
+}
+
+const RX_4 = 4;
+const RX_2 = 2;
+
+export default function QualityGateIndicator(props: Props) {
+ const {
+ className,
+ size = 'md',
+ status,
+ tooltipPlacement = PopupPlacement.Right,
+ withTooltip,
+ ariaLabel,
+ } = props;
+ const iconProps = {
+ className,
+ height: SIZE[size],
+ rx: size === 'xl' ? RX_4 : RX_2,
+ size,
+ tooltipPlacement,
+ width: SIZE[size],
+ withTooltip,
+ };
+ let StatusComponent: React.ReactNode;
+ switch (status) {
+ case 'NONE':
+ case 'NOT_COMPUTED':
+ StatusComponent = <QGNotComputed {...iconProps} />;
+ break;
+ case 'OK':
+ StatusComponent = <QGPassed {...iconProps} />;
+ break;
+ case 'ERROR':
+ StatusComponent = <QGFailed {...iconProps} />;
+ break;
+ }
+ return <div aria-label={ariaLabel}>{StatusComponent}</div>;
+}
+
+const COMMON_PROPS = {
+ fill: 'none',
+ role: 'status',
+ xmlns: 'http://www.w3.org/2000/svg',
+};
+
+interface IconProps {
+ className?: string;
+ height: string;
+ rx: number;
+ size: keyof typeof SIZE;
+ tooltipPlacement?: BasePlacement;
+ width: string;
+ withTooltip?: boolean;
+}
+
+function QGNotComputed({
+ className,
+ rx,
+ size,
+ tooltipPlacement,
+ withTooltip,
+ ...sizeProps
+}: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorNotComputed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorNotComputed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: <path d="M42 31v3H22v-3z" fill={contrastColor} />,
+ md: <path d="M18 12v1.5H6V12z" fill={contrastColor} />,
+ sm: <path d="M12 8v1H4V8z" fill={contrastColor} />,
+ }[size]
+ }
+ </svg>
+ );
+}
+
+function QGPassed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorPassed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorPassed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: (
+ <>
+ <path d="M38.974 25 41 27.026 28.847 39.178l-2.025-2.025z" fill={contrastColor} />
+ <path d="M30.974 37.153 28.95 39.18 22 32.229l2.026-2.025z" fill={contrastColor} />
+ </>
+ ),
+ md: (
+ <>
+ <path d="m16.95 7.5 1.308 1.307-7.84 7.84-1.308-1.306z" fill={contrastColor} />
+ <path d="m11.79 15.34-1.307 1.307-4.484-4.483 1.307-1.306z" fill={contrastColor} />
+ </>
+ ),
+ sm: (
+ <>
+ <path d="m11.3 5 .871.87-5.227 5.228-.87-.871z" fill={contrastColor} />
+ <path d="m7.86 10.227-.872.871L4 8.11l.871-.871z" fill={contrastColor} />
+ </>
+ ),
+ }[size]
+ }
+ </svg>
+ );
+}
+
+function QGFailed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorFailed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorFailed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: (
+ <>
+ <path d="m37.153 25 2.026 2.026-12.153 12.152L25 37.153z" fill={contrastColor} />
+ <path d="m39.178 37.153-2.025 2.026L25 27.026 27.026 25z" fill={contrastColor} />
+ </>
+ ),
+ md: (
+ <>
+ <path d="m15.34 7.5 1.307 1.307-7.84 7.84L7.5 15.34z" fill={contrastColor} />
+ <path d="m16.647 15.34-1.307 1.307-7.84-7.84L8.806 7.5z" fill={contrastColor} />
+ </>
+ ),
+ sm: (
+ <>
+ <path d="m10.227 5 .871.871-5.227 5.227L5 10.227z" fill={contrastColor} />
+ <path d="m11.098 10.227-.871.87L5 5.872 5.87 5z" fill={contrastColor} />
+ </>
+ ),
+ }[size]
+ }
+ </svg>
+ );
+}