diff --git a/src/App.jsx b/src/App.jsx
index d48f0b4..f4ff02b 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -12,6 +12,7 @@ import { ResourceContextProvider } from "./contexts/ResourceContext";
import { SnackbarProvider, closeSnackbar } from "notistack";
import { IconButton } from "@mui/material";
import Iconify from "./components/Iconify";
+import { ThemeModeContextProvider } from "./contexts/ThemeModeContext";
// ----------------------------------------------------------------------
@@ -19,24 +20,26 @@ export default function App() {
return (
- (
- closeSnackbar(snack)} color="inherit">
-
-
- )}
- dense
- preventDuplicate
- >
-
-
-
-
-
-
+
+ (
+ closeSnackbar(snack)} color="inherit">
+
+
+ )}
+ dense
+ preventDuplicate
+ >
+
+
+
+
+
+
+
);
diff --git a/src/contexts/ThemeModeContext.jsx b/src/contexts/ThemeModeContext.jsx
new file mode 100644
index 0000000..8b4bb01
--- /dev/null
+++ b/src/contexts/ThemeModeContext.jsx
@@ -0,0 +1,29 @@
+import { useMediaQuery } from "@mui/material";
+import { createContext, useState } from "react";
+
+const initialState = {
+ mode: "light",
+ };
+
+ export const ThemeModeContext = createContext({
+ ...initialState,
+ });
+
+ export const ThemeModeContextProvider = ({ children }) => {
+ const initial = useMediaQuery("(prefers-color-scheme: dark)")
+ ? "dark"
+ : "light";
+
+ const [mode, setMode] = useState(initial);
+
+ const toggleMode = () => {
+ setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
+ };
+
+ return (
+
+ {children}
+
+ );
+ };
+
\ No newline at end of file
diff --git a/src/layouts/dashboard/Menu.jsx b/src/layouts/dashboard/Menu.jsx
index ffd10fc..e247727 100644
--- a/src/layouts/dashboard/Menu.jsx
+++ b/src/layouts/dashboard/Menu.jsx
@@ -1,4 +1,4 @@
-import { useRef, useState } from "react";
+import { useContext, useRef, useState } from "react";
// keycloak
import { useKeycloak } from "@react-keycloak/web";
// @mui
@@ -16,6 +16,7 @@ import Link from "@mui/material/Link";
import { Link as RouterLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import useResource from "src/hooks/useResource";
+import { ThemeModeContext } from "src/contexts/ThemeModeContext";
// ----------------------------------------------------------------------
@@ -23,6 +24,8 @@ export default function Menu() {
const anchorRef = useRef(null);
const { t } = useTranslation();
+ const { mode, toggleMode } = useContext(ThemeModeContext);
+
const [open, setOpen] = useState(null);
const { unread } = useResource();
@@ -199,7 +202,23 @@ export default function Menu() {
{t("menu-github")}
+
+
+
+ Beta
+
+
+
+
+
{shouldRenderAdmin() && (
<>
diff --git a/src/locales/en.json b/src/locales/en.json
index c74c60c..b8f95a0 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -423,6 +423,8 @@
"shared-in-group": "Resource is available through a group you are a member of",
"auto-scroll": "Auto scroll",
"logs-truncated": "Over 1000 logs: Logs truncated. Please download the full log file to view earlier logs.",
- "no-teams": "No teams found, create one or join a friend's team!"
+ "no-teams": "No teams found, create one or join a friend's team!",
+ "dark-mode": "Dark mode",
+ "light-mode": "Light mode"
}
-}
+}
\ No newline at end of file
diff --git a/src/locales/se.json b/src/locales/se.json
index 3ebedc4..e992dfa 100644
--- a/src/locales/se.json
+++ b/src/locales/se.json
@@ -423,6 +423,8 @@
"shared-in-group": "Resursen är tillgänglig genom en av grupperna du är medlem i",
"auto-scroll": "Auto scroll",
"logs-truncated": "Över 1000 loggar: Loggar avkortade. Ladda ner hela loggen för att se tidigare loggar.",
- "no-teams": "Inga grupper hittades, skapa en eller gå med i en väns grupp!"
+ "no-teams": "Inga grupper hittades, skapa en eller gå med i en väns grupp!",
+ "dark-mode": "Mörkt läge",
+ "light-mode": "Ljust läge"
}
-}
+}
\ No newline at end of file
diff --git a/src/theme/index.jsx b/src/theme/index.jsx
index 3a13cc6..11cd812 100644
--- a/src/theme/index.jsx
+++ b/src/theme/index.jsx
@@ -1,5 +1,5 @@
import PropTypes from "prop-types";
-import { useMemo } from "react";
+import { useContext, useMemo } from "react";
// material
import { CssBaseline } from "@mui/material";
import {
@@ -8,10 +8,11 @@ import {
StyledEngineProvider,
} from "@mui/material/styles";
//
-import palette from "./palette";
+import { palette, lightPalette } from "./palette";
import typography from "./typography";
import componentsOverride from "./overrides";
import shadows, { customShadows } from "./shadows";
+import { ThemeModeContext } from "src/contexts/ThemeModeContext";
// ----------------------------------------------------------------------
@@ -20,18 +21,18 @@ ThemeProvider.propTypes = {
};
export default function ThemeProvider({ children }) {
- const themeOptions = useMemo(
- () => ({
- palette,
- shape: { borderRadius: 8 },
- typography,
- shadows,
- customShadows,
- }),
- []
- );
+ const { mode } = useContext(ThemeModeContext);
+
+ const getDesignTokens = (mode) => ({
+ palette: mode === "light" ? lightPalette : palette,
+ shape: { borderRadius: 8 },
+ typography,
+ shadows,
+ customShadows,
+ });
+
+ const theme = useMemo(() => createTheme(getDesignTokens(mode)), [mode]);
- const theme = createTheme(themeOptions);
theme.components = componentsOverride(theme);
return (
diff --git a/src/theme/palette.js b/src/theme/palette.js
index 74ab3bb..704eb7c 100644
--- a/src/theme/palette.js
+++ b/src/theme/palette.js
@@ -98,7 +98,7 @@ const CHART_COLORS = {
red: ["#FF6C40", "#FF8F6D", "#FFBD98", "#FFF2D4"],
};
-const palette = {
+export const lightPalette = {
common: { black: "#000", white: "#fff" },
primary: { ...PRIMARY },
secondary: { ...SECONDARY },
@@ -124,4 +124,58 @@ const palette = {
},
};
-export default palette;
+const DARK_GREY = {
+ 0: "#161C24",
+ 100: "#212B36",
+ 200: "#454F5B",
+ 300: "#637381",
+ 400: "#919EAB",
+ 500: "#C4CDD5",
+ 600: "#DFE3E8",
+ 700: "#F4F6F8",
+ 800: "#F9FAFB",
+ 900: "#FFFFFF",
+ 500_8: alpha("#919EAB", 0.08),
+ 500_12: alpha("#919EAB", 0.12),
+ 500_16: alpha("#919EAB", 0.16),
+ 500_24: alpha("#919EAB", 0.24),
+ 500_32: alpha("#919EAB", 0.32),
+ 500_48: alpha("#919EAB", 0.48),
+ 500_56: alpha("#919EAB", 0.56),
+ 500_80: alpha("#919EAB", 0.8),
+};
+
+export const palette = {
+ common: { black: "#000", white: "#fff" },
+ primary: { ...PRIMARY },
+ secondary: { ...SECONDARY },
+ info: { ...INFO },
+ success: { ...SUCCESS },
+ warning: { ...WARNING },
+ error: { ...ERROR },
+ grey: DARK_GREY,
+ gradients: GRADIENTS,
+ chart: CHART_COLORS,
+ divider: DARK_GREY[500_24],
+ text: {
+ primary: DARK_GREY[800],
+ secondary: DARK_GREY[600],
+ disabled: DARK_GREY[500],
+ },
+ background: {
+ paper: DARK_GREY[100],
+ default: DARK_GREY[0],
+ neutral: DARK_GREY[200],
+ },
+ action: {
+ active: DARK_GREY[600],
+ hover: DARK_GREY[500_8],
+ selected: DARK_GREY[500_16],
+ disabled: DARK_GREY[500_80],
+ disabledBackground: DARK_GREY[500_24],
+ focus: DARK_GREY[500_24],
+ hoverOpacity: 0.08,
+ disabledOpacity: 0.48,
+ },
+ mode: "dark",
+};
diff --git a/src/theme/shadows.js b/src/theme/shadows.js
index 07af8cf..ea555f4 100644
--- a/src/theme/shadows.js
+++ b/src/theme/shadows.js
@@ -1,6 +1,6 @@
// material
import { alpha } from "@mui/material/styles";
-import palette from "./palette";
+import { lightPalette as palette } from "./palette";
// ----------------------------------------------------------------------