Skip to content

Commit fb0454b

Browse files
committed
feat: Implement Dark mode support
Signed-off-by: Sneha Das <154408198+Snehadas2005@users.noreply.github.com>
1 parent e2b8366 commit fb0454b

File tree

12 files changed

+2508
-14
lines changed

12 files changed

+2508
-14
lines changed

workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
SearchInputProps,
55
} from '@patternfly/react-core/dist/esm/components/SearchInput';
66
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
7-
import { useThemeContext } from 'mod-arch-kubeflow';
7+
import { useThemeContext } from '~/app/hooks/useThemeContext';
88
import FormFieldset from '~/app/components/FormFieldset';
99

1010
type ThemeAwareSearchInputProps = Omit<SearchInputProps, 'onChange' | 'onClear'> & {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as React from 'react';
2+
import { createTheme, ThemeProvider as MUIThemeProvider } from '@mui/material/styles';
3+
import { Theme } from 'mod-arch-kubeflow';
4+
import '~/style/MUI-theme.scss';
5+
6+
export type ThemeContextProps = {
7+
isMUITheme: boolean;
8+
isDarkMode: boolean;
9+
toggleDarkMode: () => void;
10+
};
11+
12+
export const ThemeContext = React.createContext<ThemeContextProps>({
13+
isMUITheme: false,
14+
isDarkMode: false,
15+
toggleDarkMode: () => {},
16+
});
17+
18+
const DARK_MODE_STORAGE_KEY = 'kubeflow-dark-mode';
19+
20+
export const ThemeProvider: React.FC<{ theme?: Theme; children: React.ReactNode }> = ({
21+
theme = Theme.Patternfly,
22+
children,
23+
}) => {
24+
const [isDarkMode, setIsDarkMode] = React.useState<boolean>(() => {
25+
const saved = localStorage.getItem(DARK_MODE_STORAGE_KEY);
26+
return saved === 'true';
27+
});
28+
29+
const toggleDarkMode = React.useCallback(() => {
30+
setIsDarkMode((prev) => {
31+
const newVal = !prev;
32+
localStorage.setItem(DARK_MODE_STORAGE_KEY, String(newVal));
33+
return newVal;
34+
});
35+
}, []);
36+
37+
const muiTheme = React.useMemo(
38+
() =>
39+
createTheme({
40+
cssVariables: true,
41+
palette: {
42+
mode: isDarkMode ? 'dark' : 'light',
43+
},
44+
}),
45+
[isDarkMode],
46+
);
47+
48+
React.useEffect(() => {
49+
const html = document.documentElement;
50+
51+
if (theme === Theme.MUI) {
52+
html.classList.add(Theme.MUI);
53+
} else {
54+
html.classList.remove(Theme.MUI);
55+
}
56+
57+
if (isDarkMode) {
58+
html.classList.add('pf-v6-theme-dark');
59+
} else {
60+
html.classList.remove('pf-v6-theme-dark');
61+
}
62+
}, [theme, isDarkMode]);
63+
64+
const value = React.useMemo(
65+
() => ({
66+
isMUITheme: theme === Theme.MUI,
67+
isDarkMode,
68+
toggleDarkMode,
69+
}),
70+
[theme, isDarkMode, toggleDarkMode],
71+
);
72+
73+
return (
74+
<ThemeContext.Provider value={value}>
75+
{theme === Theme.MUI ? (
76+
<MUIThemeProvider theme={muiTheme}>{children}</MUIThemeProvider>
77+
) : (
78+
children
79+
)}
80+
</ThemeContext.Provider>
81+
);
82+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as React from 'react';
2+
import { ThemeContext } from '~/app/context/ThemeContext';
3+
4+
export const useThemeContext = () => {
5+
const context = React.useContext(ThemeContext);
6+
if (!context) {
7+
throw new Error('useThemeContext must be used within a ThemeProvider');
8+
}
9+
return context;
10+
};

workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit
1111
import { css } from '@patternfly/react-styles';
1212
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
1313
import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
14-
import { useThemeContext } from 'mod-arch-kubeflow';
14+
import { useThemeContext } from '~/app/hooks/useThemeContext';
1515
import { WorkspacekindsOptionLabel } from '~/generated/data-contracts';
1616
import ThemeAwareFormGroupWrapper from '~/shared/components/ThemeAwareFormGroupWrapper';
1717

workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Checkbox } from '@patternfly/react-core/dist/esm/components/Checkbox';
1515
import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText';
1616
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
1717
import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
18-
import { useThemeContext } from 'mod-arch-kubeflow';
18+
import { useThemeContext } from '~/app/hooks/useThemeContext';
1919
import ThemeAwareFormGroupWrapper from '~/shared/components/ThemeAwareFormGroupWrapper';
2020
import { generateUniqueId } from '~/app/pages/WorkspaceKinds/Form/helpers';
2121
import { isMemoryLimitLarger } from '~/shared/utilities/valueUnits';

workspaces/frontend/src/app/pages/Workspaces/Form/properties/secrets/SecretsCreateModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Select } from '@patternfly/react-core/dist/esm/components/Select';
1717
import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle';
1818
import { HelperText } from '@patternfly/react-core/dist/esm/components/HelperText';
1919
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
20-
import { useThemeContext } from 'mod-arch-kubeflow';
20+
import { useThemeContext } from '~/app/hooks/useThemeContext';
2121
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
2222
import { useNamespaceSelectorWrapper } from '~/app/hooks/useNamespaceSelectorWrapper';
2323
import { SecretsSecretListItem } from '~/generated/data-contracts';

workspaces/frontend/src/app/standalone/NavBar.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from 'react';
22
import { Brand } from '@patternfly/react-core/dist/esm/components/Brand';
3+
import { Button } from '@patternfly/react-core/dist/esm/components/Button';
34
import {
45
Dropdown,
56
DropdownItem,
@@ -26,9 +27,11 @@ import {
2627
} from '@patternfly/react-core/dist/esm/components/Toolbar';
2728
import { SimpleSelect } from '@patternfly/react-templates';
2829
import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon';
30+
import { MoonIcon } from '@patternfly/react-icons/dist/esm/icons/moon-icon';
31+
import { SunIcon } from '@patternfly/react-icons/dist/esm/icons/sun-icon';
2932
import { useNamespaceSelector, useModularArchContext } from 'mod-arch-core';
30-
import { useThemeContext } from 'mod-arch-kubeflow';
3133
import { images as sharedImages } from 'mod-arch-shared';
34+
import { useThemeContext } from '~/app/hooks/useThemeContext';
3235

3336
interface NavBarProps {
3437
username?: string;
@@ -38,7 +41,7 @@ interface NavBarProps {
3841
const NavBar: React.FC<NavBarProps> = ({ username, onLogout }) => {
3942
const { namespaces, preferredNamespace, updatePreferredNamespace } = useNamespaceSelector();
4043
const { config } = useModularArchContext();
41-
const { isMUITheme } = useThemeContext();
44+
const { isMUITheme, isDarkMode, toggleDarkMode } = useThemeContext();
4245

4346
const [userMenuOpen, setUserMenuOpen] = useState(false);
4447

@@ -99,8 +102,18 @@ const NavBar: React.FC<NavBarProps> = ({ username, onLogout }) => {
99102
/>
100103
</ToolbarItem>
101104
</ToolbarGroup>
102-
{username && (
103-
<ToolbarGroup variant="action-group-plain" align={{ default: 'alignEnd' }}>
105+
<ToolbarGroup variant="action-group-plain" align={{ default: 'alignEnd' }}>
106+
{isMUITheme && (
107+
<ToolbarItem>
108+
<Button
109+
variant="plain"
110+
aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
111+
onClick={toggleDarkMode}
112+
icon={isDarkMode ? <SunIcon /> : <MoonIcon />}
113+
/>
114+
</ToolbarItem>
115+
)}
116+
{username && (
104117
<ToolbarItem>
105118
<Dropdown
106119
popperProps={{ position: 'right' }}
@@ -122,8 +135,8 @@ const NavBar: React.FC<NavBarProps> = ({ username, onLogout }) => {
122135
<DropdownList>{userMenuItems}</DropdownList>
123136
</Dropdown>
124137
</ToolbarItem>
125-
</ToolbarGroup>
126-
)}
138+
)}
139+
</ToolbarGroup>
127140
</ToolbarContent>
128141
</Toolbar>
129142
</MastheadContent>

workspaces/frontend/src/app/standalone/NavSidebar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
NavList,
99
} from '@patternfly/react-core/dist/esm/components/Nav';
1010
import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page';
11-
import { useThemeContext, images as kubeflowImages } from 'mod-arch-kubeflow';
11+
import { images as kubeflowImages } from 'mod-arch-kubeflow';
12+
import { useThemeContext } from '~/app/hooks/useThemeContext';
1213
import { isNavDataGroup, NavDataHref, NavDataGroup } from '~/app/standalone/types';
1314
import { useTypedLocation } from '~/app/routerHelper';
1415
import { useNavData } from '~/app/AppRoutes';

workspaces/frontend/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ModularArchContextProvider,
88
NotificationContextProvider,
99
} from 'mod-arch-core';
10-
import { ThemeProvider } from 'mod-arch-kubeflow';
10+
import { ThemeProvider } from '~/app/context/ThemeContext';
1111
import App from './app/App';
1212
import {
1313
DEPLOYMENT_MODE,

workspaces/frontend/src/shared/components/DeleteModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'
1313
import { FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex';
1414
import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText';
1515
import { default as ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
16-
import { useThemeContext } from 'mod-arch-kubeflow';
16+
import { useThemeContext } from '~/app/hooks/useThemeContext';
1717
import { ActionButton } from '~/shared/components/ActionButton';
1818
import { ErrorAlert } from '~/shared/components/ErrorAlert';
1919
import ThemeAwareFormGroupWrapper from '~/shared/components/ThemeAwareFormGroupWrapper';

0 commit comments

Comments
 (0)