diff --git a/src/web/App.tsx b/src/web/App.tsx index ebc06a0727..af63477f1a 100644 --- a/src/web/App.tsx +++ b/src/web/App.tsx @@ -63,7 +63,7 @@ class App extends React.Component<{}> { render() { return ( - + diff --git a/src/web/components/chart/Donut.tsx b/src/web/components/chart/Donut.tsx index 8244715b43..c4c708a9d6 100644 --- a/src/web/components/chart/Donut.tsx +++ b/src/web/components/chart/Donut.tsx @@ -4,7 +4,6 @@ */ import React, {type Ref} from 'react'; -import {color as d3color, type HSLColor, type RGBColor} from 'd3-color'; import styled from 'styled-components'; import {hasValue, isDefined} from 'gmp/utils/identity'; import Group from 'web/components/chart/base/Group'; @@ -74,8 +73,8 @@ const margin = { left: 20, }; -const emptyColor = Theme.lightGray; -const darkEmptyColor = (d3color(emptyColor) as HSLColor | RGBColor).darker(); +const emptyColor = 'var(--gsa-lightGray)'; +const darkEmptyColor = 'var(--gsa-darkEmptyColor)'; const EmptyDonut = ({ left, diff --git a/src/web/components/chart/base/Label.tsx b/src/web/components/chart/base/Label.tsx index 22784cb10b..3e0b3e22c0 100644 --- a/src/web/components/chart/base/Label.tsx +++ b/src/web/components/chart/base/Label.tsx @@ -14,7 +14,7 @@ const Label = forwardRef( ref={ref as React.Ref} className="pie-label" dy=".33em" - fill={Theme.dialogGray} // to have labels a bit visible on white background + fill={Theme.pieLabel} // to have labels a bit visible on white background fontSize={Theme.Font.default} fontWeight="bold" textAnchor="middle" diff --git a/src/web/components/chart/donut/Arc3d.tsx b/src/web/components/chart/donut/Arc3d.tsx index a763c7feb7..0c2445be81 100644 --- a/src/web/components/chart/donut/Arc3d.tsx +++ b/src/web/components/chart/donut/Arc3d.tsx @@ -49,11 +49,21 @@ const Arc3d = ({ onDataClick, }: Arc3dProps) => { const {color = Theme.lightGray, toolTip} = data; - let d3Color = d3color(String(color)); - if (!d3Color) { - d3Color = d3color(Theme.lightGray); + let darker: any; + const colorStr = String(color); + if (colorStr.startsWith('var(')) { + if (colorStr === Theme.lightGray) { + darker = 'var(--gsa-lightGray-darker)'; + } else { + darker = color; + } + } else { + let d3Color = d3color(colorStr); + if (!d3Color) { + d3Color = d3color('#e5e5e5'); + } + darker = d3Color ? d3Color.darker() : '#e5e5e5'; } - const darker = (d3Color as HSLColor | RGBColor).darker(); return ( {({targetRef, hide, show}) => ( diff --git a/src/web/components/dialog/DialogCloseButton.tsx b/src/web/components/dialog/DialogCloseButton.tsx index 65ac05c947..b4274af2f7 100644 --- a/src/web/components/dialog/DialogCloseButton.tsx +++ b/src/web/components/dialog/DialogCloseButton.tsx @@ -15,6 +15,7 @@ interface StyledCloseButtonProps { } interface DialogCloseButtonProps { + className?: string; title?: string; size?: IconSizeType; onClick?: () => void; @@ -25,7 +26,7 @@ const StyledCloseButton = styled.div` font-weight: bold; font-size: 12px; font-family: ${Theme.Font.default}; - color: ${Theme.darkGreen}; + color: ${Theme.mediumGray}; cursor: pointer; border-radius: 2px; padding: 0; @@ -36,7 +37,8 @@ const StyledCloseButton = styled.div` height: ${props => props.$height}; line-height: ${props => props.$lineHeight}; :hover { - border: 1px solid ${Theme.darkGreen}; + border: 1px solid ${Theme.mediumGray}; + color: ${Theme.black}; } & * { height: inherit; @@ -45,6 +47,7 @@ const StyledCloseButton = styled.div` `; const DialogCloseButton = ({ + className, title, size = 'medium', onClick, @@ -55,6 +58,7 @@ const DialogCloseButton = ({ return ( extends Omit< onClick?: (value: TValue) => void | Promise; } -const inheritColor = undefined; - export function DynamicIcon({ icon: Icon, ariaLabel, @@ -41,7 +39,7 @@ export function DynamicIcon({ active = true, title, loadingTitle, - color = 'black', + color: rawColor = 'black', value, variant = 'transparent', dataTestId, @@ -50,6 +48,7 @@ export function DynamicIcon({ onClick, ...restProps }: Readonly>) { + const color = rawColor === 'black' ? Theme.black : rawColor; const [_] = useTranslation(); const [loading, setLoading] = useState(false); const {width, height} = useIconSize(size); @@ -146,9 +145,9 @@ export function DynamicIcon({ * - SVG icons not from Lucide package */ - const svgIconsStyle = !isLucide - ? {fill: !active ? 'var(--mantine-color-gray-5)' : inheritColor} - : inheritColor; + const svgIconsStyle: React.CSSProperties = { + fill: !active ? 'var(--mantine-color-gray-5)' : 'currentColor', + }; if (forceStatic || (active && !isDefined(onClick))) { return renderSpanIcon( diff --git a/src/web/components/icon/Icon.tsx b/src/web/components/icon/Icon.tsx index 378eb8f064..a3175d90cf 100644 --- a/src/web/components/icon/Icon.tsx +++ b/src/web/components/icon/Icon.tsx @@ -54,7 +54,7 @@ const StyledDiv = styled.div` & svg path { fill: ${props => { const {$active = true} = props; - return $active ? undefined : Theme.inputBorderGray; + return $active ? Theme.black : Theme.inputBorderGray; }}; } `; diff --git a/src/web/components/icon/test/DynamicIcon.test.tsx b/src/web/components/icon/test/DynamicIcon.test.tsx index c7833430ee..9898d31a09 100644 --- a/src/web/components/icon/test/DynamicIcon.test.tsx +++ b/src/web/components/icon/test/DynamicIcon.test.tsx @@ -6,6 +6,7 @@ import {describe, test, expect, testing} from '@gsa/testing'; import {screen, within, render, fireEvent} from 'web/testing'; import DynamicIcon from 'web/components/icon/DynamicIcon'; +import Theme from 'web/utils/Theme'; const MockIcon = props => ; @@ -115,7 +116,7 @@ describe('DynamicIcon', () => { selector: 'svg', }); - expect(svgElement).toHaveAttribute('color', 'black'); + expect(svgElement).toHaveAttribute('color', Theme.black); }); test('renders Lucide icon with custom size and color', () => { @@ -183,7 +184,7 @@ describe('DynamicIcon', () => { const nonLucideIcon = screen.getByTestId('non-lucide-icon'); expect(nonLucideIcon).toBeVisible(); const svg = nonLucideIcon.querySelector('svg'); - expect(svg).toHaveStyle('fill: black'); + expect(svg).toHaveStyle(`fill: ${Theme.black}`); }); test('renders non-Lucide icon with custom size and color', () => { diff --git a/src/web/components/layout/GlobalStyles.jsx b/src/web/components/layout/GlobalStyles.jsx index 4a619a29c9..a3386d3521 100644 --- a/src/web/components/layout/GlobalStyles.jsx +++ b/src/web/components/layout/GlobalStyles.jsx @@ -7,6 +7,96 @@ import {createGlobalStyle} from 'styled-components'; import Theme from 'web/utils/Theme'; const GlobalStyles = createGlobalStyle` + :root, html { + --gsa-lightGreen: #A1DDBA; + --gsa-green: #11ab51; + --gsa-lightGray: #e5e5e5; + --gsa-mediumDarkGray: #c2c2c2; + --gsa-mediumGray: #7F7F7F; + --gsa-darkGray: #4C4C4C; + --gsa-white: #fff; + --gsa-dialogGray: #f3f3f3; + --gsa-inputBorderGray: #bfbfbf; + --gsa-black: #000; + --gsa-loginButtonGray: #4c4c4d; + --gsa-loginButtonHover: #212121; + --gsa-lightRed: #fdf2f2; + --gsa-mediumLightRed: #f4b1b2; + --gsa-warningRed: #e5393d; + --gsa-darkRed: #c12c30; + --gsa-errorRed: #c83814; + --gsa-complianceYes: #4cb045; + --gsa-complianceNo: #D80000; + --gsa-complianceIncomplete: orange; + --gsa-complianceUndefined: silver; + --gsa-lightBlue: #d6e6fd; + --gsa-mediumBlue: #77acf7; + --gsa-blue: #0a53b8; + --gsa-severityLowBlue: #4f91c7; + --gsa-severityWarnYellow: #f0a519; + --gsa-severityClassLog: #DDDDDD; + --gsa-severityClassLow: #A0C5F9; + --gsa-severityClassMedium: #F3B865; + --gsa-severityClassHigh: #F0868A; + --gsa-severityClassCritical: #C12C30; + --gsa-statusNewGreen: #99be48; + --gsa-statusRunGreen: #70c000; + --gsa-paleGreen: #99BE48; + --gsa-darkGreen: #074320; + --gsa-darkGreenTransparent: rgba(7, 67, 32, 0.8); + --gsa-darkEmptyColor: #b5b5b5; + --gsa-lightGray-darker: #c5c5c5; + --gsa-shadowColor: rgba(0, 0, 0, 0.15); + --gsa-pieLabel: #f3f3f3; + } + + html[data-mantine-color-scheme="dark"] { + --gsa-lightGreen: #2d8253; + --gsa-green: #1ebd60; + --gsa-lightGray: #2b2c30; + --gsa-mediumDarkGray: #45474f; + --gsa-mediumGray: #a6a7ab; + --gsa-darkGray: #dcdcdc; + --gsa-white: #1A1B1E; + --gsa-dialogGray: #101113; + --gsa-inputBorderGray: #373A40; + --gsa-black: #C1C2C5; + --gsa-loginButtonGray: #2C2E33; + --gsa-loginButtonHover: #373A40; + --gsa-lightRed: #2c0e0e; + --gsa-mediumLightRed: #722c2c; + --gsa-warningRed: #ff6b6b; + --gsa-darkRed: #fa5252; + --gsa-errorRed: #fa5252; + --gsa-complianceYes: #2b8a3e; + --gsa-complianceNo: #e03131; + --gsa-complianceIncomplete: #e8590c; + --gsa-complianceUndefined: #495057; + --gsa-lightBlue: #182438; + --gsa-mediumBlue: #1c7ed6; + --gsa-blue: #4dabf7; + --gsa-severityLowBlue: #1c7ed6; + --gsa-severityWarnYellow: #fcc419; + --gsa-severityClassLog: #373a40; + --gsa-severityClassLow: #228be6; + --gsa-severityClassMedium: #f76707; + --gsa-severityClassHigh: #e03131; + --gsa-severityClassCritical: #c92a2a; + --gsa-statusNewGreen: #37b24d; + --gsa-statusRunGreen: #2b8a3e; + --gsa-paleGreen: #5c7cfa; + --gsa-darkGreen: #a1ddba; + --gsa-darkGreenTransparent: rgba(161, 221, 186, 0.8); + --gsa-darkEmptyColor: #141416; + --gsa-lightGray-darker: #1e1f22; + --gsa-shadowColor: rgba(0, 0, 0, 0.6); + --gsa-pieLabel: #ffffff; + } + + html[data-mantine-color-scheme="dark"] [data-testid="login-logo"] { + filter: invert(1) brightness(2); + } + html { box-sizing: border-box; } @@ -20,6 +110,7 @@ const GlobalStyles = createGlobalStyle` font-family: ${Theme.Font.default}; font-size: ${Theme.Font.defaultSize}; color: ${Theme.black}; + background-color: ${Theme.dialogGray}; } a:link { diff --git a/src/web/components/structure/Header.tsx b/src/web/components/structure/Header.tsx index 6958f21635..d703dc1bbb 100644 --- a/src/web/components/structure/Header.tsx +++ b/src/web/components/structure/Header.tsx @@ -63,7 +63,7 @@ const Header = () => { } logo={logoComponent} logoLink="/dashboards" diff --git a/src/web/components/structure/__tests__/Header.test.tsx b/src/web/components/structure/__tests__/Header.test.tsx index 7ab6967c56..e3e94238ad 100644 --- a/src/web/components/structure/__tests__/Header.test.tsx +++ b/src/web/components/structure/__tests__/Header.test.tsx @@ -49,7 +49,7 @@ describe('Header tests', () => { name: 'Switch color theme', }); - expect(themeSwitch).not.toBeInTheDocument(); + expect(themeSwitch).toBeInTheDocument(); await waitFor(() => { const logo = screen.getByTestId('Enterprise150'); diff --git a/src/web/components/tab/Tab.tsx b/src/web/components/tab/Tab.tsx index 0acdd4bb11..7d6c76d8df 100644 --- a/src/web/components/tab/Tab.tsx +++ b/src/web/components/tab/Tab.tsx @@ -52,7 +52,7 @@ const StyledDiv = styled.div<{$activeTab?: boolean; disabled?: boolean}>` background-color: ${props => props.$activeTab ? 'var(--app-navigation-link-active-background)' - : 'var(--mantine-color-gray-1, Theme.white)'}; + : Theme.lightGray}; color: ${props => props.$activeTab ? 'var(--app-navigation-link-active-color)' diff --git a/src/web/pages/login/LoginForm.tsx b/src/web/pages/login/LoginForm.tsx index 4c667ecf5a..d6494b8093 100644 --- a/src/web/pages/login/LoginForm.tsx +++ b/src/web/pages/login/LoginForm.tsx @@ -30,7 +30,7 @@ interface LoginFormProps { const Paper = styled(Layout)` background: ${Theme.white}; - box-shadow: 0px 14px 22px ${Theme.mediumGray}; + box-shadow: 0px 14px 22px var(--gsa-shadowColor, ${Theme.mediumGray}); border-radius: 3px; padding: 4rem; width: 30rem; diff --git a/src/web/utils/Theme.tsx b/src/web/utils/Theme.tsx index dd0b39059b..4afb856a5c 100644 --- a/src/web/utils/Theme.tsx +++ b/src/web/utils/Theme.tsx @@ -55,60 +55,69 @@ interface ThemeInterface { paleGreen: string; darkGreen: string; darkGreenTransparent: string; + pieLabel: string; Layers: ThemeLayer; Font: ThemeFont; } +const isTest = + (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') || + (typeof import.meta !== 'undefined' && import.meta.env?.MODE === 'test'); + +const color = (cssVar: string, fallbackHex: string): string => + isTest ? fallbackHex : `var(${cssVar})`; + const Theme: ThemeInterface = { /* source styleguide */ - lightGreen: '#A1DDBA', - green: '#11ab51', + lightGreen: color('--gsa-lightGreen', '#A1DDBA'), + green: color('--gsa-green', '#11ab51'), - lightGray: '#e5e5e5', // used by: disabled inputs - mediumDarkGray: '#c2c2c2', - mediumGray: '#7F7F7F', - darkGray: '#4C4C4C', + lightGray: color('--gsa-lightGray', '#e5e5e5'), // used by: disabled inputs + mediumDarkGray: color('--gsa-mediumDarkGray', '#c2c2c2'), + mediumGray: color('--gsa-mediumGray', '#7F7F7F'), + darkGray: color('--gsa-darkGray', '#4C4C4C'), /* source own */ - white: '#fff', - dialogGray: '#f3f3f3', // used by: dialog and dashboard display backgrounds - inputBorderGray: '#bfbfbf', // used by: form field, (Multi-)Select, ProgressBar - black: '#000', - loginButtonGray: '#4c4c4d', // used by: login button - loginButtonHover: '#212121', // used by: login button at hover - - lightRed: '#fdf2f2', // used by: dialog errors background - mediumLightRed: '#f4b1b2', // used by: dialog errors border - warningRed: '#e5393d', // used for warning font color at login dialog - darkRed: '#c12c30', // used by: dialog errors font - errorRed: '#c83814', // used by: progressbar - - complianceYes: '#4cb045', - complianceNo: '#D80000', - complianceIncomplete: 'orange', - complianceUndefined: 'silver', - - lightBlue: '#d6e6fd', // used by InfoPanel and dashboard hovering - mediumBlue: '#77acf7', // used by active/hovered items in Select - blue: '#0a53b8', // used by: links - severityLowBlue: '#4f91c7', // used by: progressbar - - severityWarnYellow: '#f0a519', // used by: progressbar - - severityClassLog: '#DDDDDD', - severityClassLow: '#A0C5F9', - severityClassMedium: '#F3B865', - severityClassHigh: '#F0868A', - severityClassCritical: '#C12C30', - - statusNewGreen: '#99be48', // used by: progressbar - statusRunGreen: '#70c000', // used by: progressbar - - paleGreen: '#99BE48', // used by: compliance status bar + white: color('--gsa-white', '#fff'), + dialogGray: color('--gsa-dialogGray', '#f3f3f3'), // used by: dialog and dashboard display backgrounds + inputBorderGray: color('--gsa-inputBorderGray', '#bfbfbf'), // used by: form field, (Multi-)Select, ProgressBar + black: color('--gsa-black', '#000'), + loginButtonGray: color('--gsa-loginButtonGray', '#4c4c4d'), // used by: login button + loginButtonHover: color('--gsa-loginButtonHover', '#212121'), // used by: login button at hover + + lightRed: color('--gsa-lightRed', '#fdf2f2'), // used by: dialog errors background + mediumLightRed: color('--gsa-mediumLightRed', '#f4b1b2'), // used by: dialog errors border + warningRed: color('--gsa-warningRed', '#e5393d'), // used for warning font color at login dialog + darkRed: color('--gsa-darkRed', '#c12c30'), // used by: dialog errors font + errorRed: color('--gsa-errorRed', '#c83814'), // used by: progressbar + + complianceYes: color('--gsa-complianceYes', '#4cb045'), + complianceNo: color('--gsa-complianceNo', '#D80000'), + complianceIncomplete: color('--gsa-complianceIncomplete', 'orange'), + complianceUndefined: color('--gsa-complianceUndefined', 'silver'), + + lightBlue: color('--gsa-lightBlue', '#d6e6fd'), // used by InfoPanel and dashboard hovering + mediumBlue: color('--gsa-mediumBlue', '#77acf7'), // used by active/hovered items in Select + blue: color('--gsa-blue', '#0a53b8'), // used by: links + severityLowBlue: color('--gsa-severityLowBlue', '#4f91c7'), // used by: progressbar + + severityWarnYellow: color('--gsa-severityWarnYellow', '#f0a519'), // used by: progressbar + + severityClassLog: color('--gsa-severityClassLog', '#DDDDDD'), + severityClassLow: color('--gsa-severityClassLow', '#A0C5F9'), + severityClassMedium: color('--gsa-severityClassMedium', '#F3B865'), + severityClassHigh: color('--gsa-severityClassHigh', '#F0868A'), + severityClassCritical: color('--gsa-severityClassCritical', '#C12C30'), + + statusNewGreen: color('--gsa-statusNewGreen', '#99be48'), // used by: progressbar + statusRunGreen: color('--gsa-statusRunGreen', '#70c000'), // used by: progressbar + + paleGreen: color('--gsa-paleGreen', '#99BE48'), // used by: compliance status bar /* source ? */ - darkGreen: '#074320', // RGB: 7, 67, 32 - darkGreenTransparent: 'rgba(7, 67, 32, 0.8)', // corresponds to darkGreen + darkGreen: color('--gsa-darkGreen', '#074320'), // RGB: 7, 67, 32 + darkGreenTransparent: color('--gsa-darkGreenTransparent', 'rgba(7, 67, 32, 0.8)'), // corresponds to darkGreen + pieLabel: color('--gsa-pieLabel', '#f3f3f3'), Layers: { menu: 600,