Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<html lang="en" class="pf-v6-u-h-100">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
Expand Down
4 changes: 2 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"scripts": {
"start": "PORT=3001 HTTPS=true NODE_OPTIONS=--openssl-legacy-provider EXTEND_ESLINT=true vite",
"build": "NODE_OPTIONS=--openssl-legacy-provider EXTEND_ESLINT=true vite build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"test": "echo 'Someone should write some tests'",
"lint-check": "npm-run-all lint-check:*",
"lint-check:non-src": "prettier --check '**/*.{md,css,json}'",
"lint-check:src": "eslint --ext .js,.jsx,.ts,.tsx ./",
Expand All @@ -30,6 +29,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^8.0.0",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.29.0",
"yup": "^1.3.3"
},
Expand Down
19 changes: 19 additions & 0 deletions ui/src/components/ThemeToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { ReactElement } from 'react';
import { Button, Tooltip } from '@patternfly/react-core';
import { useTheme } from 'utils/ThemeProvider';
import { MoonIcon, SunIcon } from '@patternfly/react-icons';

const ThemeToggleButton = (): ReactElement => {
const themeState = useTheme();
const tooltipText = themeState.isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode';

return (
<Tooltip content={<div>{tooltipText}</div>} position="bottom">
<Button aria-label="Invert theme" onClick={themeState.toggle} variant="control">
{themeState.isDarkMode ? <SunIcon /> : <MoonIcon />}
</Button>
</Tooltip>
);
};

export default ThemeToggleButton;
29 changes: 16 additions & 13 deletions ui/src/containers/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DownloadsPage from 'containers/DownloadsPage';
import LaunchClusterPage from 'containers/LaunchClusterPage';
import ClusterInfoPage from 'containers/ClusterInfoPage';
import FourOhFour from 'components/FourOhFour';
import { ThemeProvider } from 'utils/ThemeProvider';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -34,19 +35,21 @@ function AppRoutes(): ReactElement {
export default function App(): ReactElement {
return (
<Router>
<UserAuthProvider>
<QueryClientProvider client={queryClient}>
<Flex
direction={{ default: 'column' }}
flexWrap={{ default: 'nowrap' }}
className="pf-v6-u-h-100 pf-v6-u-w-100"
>
<Page masthead={<AppHeader />}>
<AppRoutes />
</Page>
</Flex>
</QueryClientProvider>
</UserAuthProvider>
<ThemeProvider>
<UserAuthProvider>
<QueryClientProvider client={queryClient}>
<Flex
direction={{ default: 'column' }}
flexWrap={{ default: 'nowrap' }}
className="pf-v6-u-h-100 pf-v6-u-w-100"
>
<Page masthead={<AppHeader />}>
<AppRoutes />
</Page>
</Flex>
</QueryClientProvider>
</UserAuthProvider>
</ThemeProvider>
</Router>
);
}
15 changes: 0 additions & 15 deletions ui/src/containers/AppHeader/AppHeader.test.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions ui/src/containers/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OutlinedHandPointRightIcon, TerminalIcon } from '@patternfly/react-icon
import AppHeaderLayout from 'components/AppHeaderLayout';
import RHACSLogo from 'components/RHACSLogo';
import { useUserAuth } from 'containers/UserAuthProvider';
import ThemeToggleButton from 'components/ThemeToggleButton';

export default function AppHeader(): ReactElement {
const { user, logout } = useUserAuth();
Expand All @@ -28,6 +29,9 @@ export default function AppHeader(): ReactElement {
}
ending={
<Flex alignItems={{ default: 'alignItemsCenter' }}>
<div className="pf-v6-u-mr-md">
<ThemeToggleButton />
</div>
{user?.Picture ? (
<Avatar alt={user?.Name || 'anonymous'} src={user.Picture} size="md" isBordered />
) : (
Expand Down
4 changes: 4 additions & 0 deletions ui/src/infra.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
fill: #000000;
}

html.pf-v6-theme-dark .rhacs-logo-text {
fill: #ffffff;
}

/* Form help popovers - rendered with react-markdown */

/* #, ##, ### */
Expand Down
82 changes: 82 additions & 0 deletions ui/src/utils/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { useMediaQuery } from 'react-responsive';

const defaultContextData = {
isDarkMode: false,
toggle: () => {},
};

export const ThemeContext = createContext(defaultContextData);
export const useTheme = () => useContext(ThemeContext);

const DARK_MODE_KEY = 'isDarkMode';

type ThemeState = {
isDarkMode: boolean;
hasThemeMounted: boolean;
};

// custom react hook to toggle dark mode across UI
function useEffectDarkMode(): [ThemeState, (next: ThemeState) => void] {
const userPrefersDarkMode = useMediaQuery({ query: '(prefers-color-scheme: dark)' });
const [themeState, setThemeState] = useState<ThemeState>({
isDarkMode: userPrefersDarkMode,
hasThemeMounted: false,
});
useEffect(() => {
const darkModeValue = localStorage.getItem(DARK_MODE_KEY);
let isDarkMode;
// In the very beginning, default to using what the user prefers.
if (darkModeValue === null) {
isDarkMode = userPrefersDarkMode;
} else {
// It's always either 'true' or 'false', but if it's something unexpected,
// default to light mode.
isDarkMode = darkModeValue === 'true';
}

setThemeState({ isDarkMode, hasThemeMounted: true });
}, [userPrefersDarkMode]);

return [themeState, setThemeState];
}

export function ThemeProvider({ children }: { children: ReactNode }) {
const [themeState, setThemeState] = useEffectDarkMode();

// to prevent theme flicker while getting theme from localStorage
if (!themeState.hasThemeMounted) {
return <div />;
}

if (themeState.isDarkMode) {
document.documentElement.classList.add('pf-v6-theme-dark');
} else {
document.documentElement.classList.remove('pf-v6-theme-dark');
}

const toggle = () => {
const darkModeToggled = !themeState.isDarkMode;

localStorage.setItem(DARK_MODE_KEY, JSON.stringify(darkModeToggled));

if (themeState.isDarkMode) {
document.documentElement.classList.add('pf-v6-theme-dark');
} else {
document.documentElement.classList.remove('pf-v6-theme-dark');
}

setThemeState({ ...themeState, isDarkMode: darkModeToggled });
};

return (
<ThemeContext.Provider
value={{
isDarkMode: themeState.isDarkMode,
toggle,
}}
>
{children}
</ThemeContext.Provider>
);
}
34 changes: 33 additions & 1 deletion ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,11 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"

css-mediaquery@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==

css.escape@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
Expand Down Expand Up @@ -3556,6 +3561,11 @@ http-proxy@^1.18.1:
follow-redirects "^1.0.0"
requires-port "^1.0.0"

hyphenate-style-name@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==

ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
Expand Down Expand Up @@ -4088,6 +4098,13 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=

matchmediaquery@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.4.2.tgz#22582bd4ae63ad9f54c53001bba80cbed0f7eafa"
integrity sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==
dependencies:
css-mediaquery "^0.1.2"

math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
Expand Down Expand Up @@ -4736,7 +4753,7 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==

prop-types@^15.0.0, prop-types@^15.8.1:
prop-types@^15.0.0, prop-types@^15.6.1, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
Expand Down Expand Up @@ -4835,6 +4852,16 @@ react-refresh@^0.14.2:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==

react-responsive@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-10.0.0.tgz#657c7a90823cd565f43aa5918bd8eb0cd2c91c91"
integrity sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==
dependencies:
hyphenate-style-name "^1.0.0"
matchmediaquery "^0.4.2"
prop-types "^15.6.1"
shallow-equal "^3.1.0"

react-router-dom@^6.29.0:
version "6.29.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.29.0.tgz#2ffb56b03ef3d6d6daafcfad9f3922132d2ced94"
Expand Down Expand Up @@ -5165,6 +5192,11 @@ set-proto@^1.0.0:
es-errors "^1.3.0"
es-object-atoms "^1.0.0"

shallow-equal@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-3.1.0.tgz#e7a54bac629c7f248eff6c2f5b63122ba4320bec"
integrity sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==

shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
Expand Down
Loading