Enabled Dark Mode in Console (#3129)

- Dark mode will be tied to system settings if not set
- Dark mode will be stored in Application storage once set

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-11-15 13:33:05 -06:00
committed by GitHub
parent 0053658d5d
commit 044c265423
9 changed files with 344 additions and 253 deletions

View File

@@ -10,7 +10,7 @@
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.21",
"luxon": "^3.4.3",
"mds": "https://github.com/minio/mds.git#v0.12.1",
"mds": "https://github.com/minio/mds.git#v0.12.2",
"react": "^18.1.0",
"react-component-export-image": "^1.0.6",
"react-copy-to-clipboard": "^5.0.2",

View File

@@ -31,6 +31,7 @@ const StyleHandler = ({ children }: IStyleHandler) => {
const colorVariants = useSelector(
(state: AppState) => state.system.overrideStyles,
);
const darkMode = useSelector((state: AppState) => state.system.darkMode);
let thm = undefined;
@@ -38,11 +39,12 @@ const StyleHandler = ({ children }: IStyleHandler) => {
thm = generateOverrideTheme(colorVariants);
}
// ThemeHandler is needed for MDS components theming. Eventually we will remove Theme Provider & use only mds themes.
return (
<Fragment>
<GlobalStyles />
<ThemeHandler customTheme={thm}>{children}</ThemeHandler>
<ThemeHandler darkMode={darkMode} customTheme={thm}>
{children}
</ThemeHandler>
</Fragment>
);
};

View File

@@ -0,0 +1,48 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Button, DarkModeIcon } from "mds";
import TooltipWrapper from "../TooltipWrapper/TooltipWrapper";
import { useSelector } from "react-redux";
import { AppState, useAppDispatch } from "../../../../store";
import { setDarkMode } from "../../../../systemSlice";
import { storeDarkMode } from "../../../../utils/stylesUtils";
const DarkModeActivator = () => {
const dispatch = useAppDispatch();
const darkMode = useSelector((state: AppState) => state.system.darkMode);
const darkModeActivator = () => {
const currentStatus = !!darkMode;
dispatch(setDarkMode(!currentStatus));
storeDarkMode(!currentStatus ? "on" : "off");
};
return (
<TooltipWrapper tooltip={`${darkMode ? "Light" : "Dark"} Mode`}>
<Button
id={"dark-mode-activator"}
icon={<DarkModeIcon />}
onClick={darkModeActivator}
/>
</TooltipWrapper>
);
};
export default DarkModeActivator;

View File

@@ -17,6 +17,7 @@
import React, { Fragment } from "react";
import { PageHeader } from "mds";
import ObjectManagerButton from "../ObjectManager/ObjectManagerButton";
import DarkModeActivator from "../DarkModeActivator/DarkModeActivator";
interface IPageHeaderWrapper {
label: React.ReactNode;
@@ -35,6 +36,7 @@ const PageHeaderWrapper = ({
actions={
<Fragment>
{actions}
<DarkModeActivator />
<ObjectManagerButton />
</Fragment>
}

View File

@@ -36,6 +36,8 @@ import {
PAID_PLANS,
STANDARD_PLAN_FEATURES,
} from "./utils";
import styled from "styled-components";
import get from "lodash/get";
interface IRegisterStatus {
activateProductModal: any;
@@ -45,6 +47,252 @@ interface IRegisterStatus {
setActivateProductModal: any;
}
const PlanListContainer = styled.div(({ theme }) => ({
display: "grid",
margin: "0 1.5rem 0 1.5rem",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
[`@media (max-width: ${breakPoints.sm}px)`]: {
gridTemplateColumns: "1fr 1fr 1fr",
},
"&.paid-plans-only": {
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
},
"& .features-col": {
flex: 1,
minWidth: "260px",
"@media (max-width: 600px)": {
display: "none",
},
},
"& .xs-only": {
display: "none",
},
"& .button-box": {
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "5px 0px 25px 0px",
borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
},
"& .plan-header": {
height: "99px",
borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
},
"& .feature-title": {
height: "25px",
paddingLeft: "26px",
fontSize: "14px",
background: get(theme, "signalColors.disabled", "#E5E5E5"),
color: get(theme, "signalColors.main", "#07193E"),
"@media (max-width: 600px)": {
"& .feature-title-info .xs-only": {
display: "block",
},
},
},
"& .feature-name": {
minHeight: "60px",
padding: "5px",
borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
display: "flex",
alignItems: "center",
paddingLeft: "26px",
fontSize: "14px",
},
"& .feature-item": {
display: "flex",
flexFlow: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "60px",
padding: "0 15px 0 15px",
borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
fontSize: "14px",
"& .link-text": {
color: "#2781B0",
cursor: "pointer",
textDecoration: "underline",
},
"&.icon-yes": {
width: "15px",
height: "15px",
},
},
"& .feature-item-info": {
flex: 1,
display: "flex",
flexFlow: "column",
alignItems: "center",
justifyContent: "space-around",
textAlign: "center",
"@media (max-width: 600px)": {
justifyContent: "space-evenly",
width: "100%",
"& .xs-only": {
display: "block",
},
"& .plan-feature": {
textAlign: "center",
paddingRight: "10px",
},
},
},
"& .plan-col": {
minWidth: "260px",
flex: 1,
},
"& .active-plan-col": {
background: `${get(
theme,
"boxBackground",
"#FDFDFD",
)} 0% 0% no-repeat padding-box`,
boxShadow: " 0px 3px 20px #00000038",
"& .plan-header": {
backgroundColor: get(theme, "signalColors.info", "#2781B0"),
},
"& .feature-title": {
background: get(theme, "signalColors.disabled", "#E5E5E5"),
color: get(theme, "fontColor", "#000"),
},
},
}));
const PlanHeaderContainer = styled.div(({ theme }) => ({
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
flexFlow: "column",
borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
borderBottom: "0px !important",
"& .plan-header": {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexFlow: "column",
},
"& .title-block": {
display: "flex",
alignItems: "center",
flexFlow: "column",
width: "100%",
"& .title-main": {
display: "flex",
alignItems: "center",
justifyContent: "center",
flex: 1,
},
"& .iconContainer": {
"& .min-icon": {
minWidth: 140,
width: "100%",
maxHeight: 55,
height: "100%",
},
},
},
"& .open-source": {
fontSize: "14px",
display: "flex",
marginBottom: "5px",
alignItems: "center",
"& .min-icon": {
marginRight: "8px",
height: "12px",
width: "12px",
},
},
"& .cur-plan-text": {
fontSize: "12px",
textTransform: "uppercase",
},
"@media (max-width: 600px)": {
cursor: "pointer",
"& .title-block": {
"& .title": {
fontSize: "14px",
fontWeight: 600,
},
},
},
"&.active, &.active.xs-active": {
color: "#ffffff",
position: "relative",
"& .min-icon": {
fill: "#ffffff",
},
"&:before": {
content: "' '",
position: "absolute",
width: "100%",
height: "18px",
backgroundColor: get(theme, "signalColors.info", "#2781B0"),
display: "block",
top: -16,
},
"& .iconContainer": {
"& .min-icon": {
marginTop: "-12px",
},
},
},
"&.active": {
backgroundColor: get(theme, "signalColors.info", "#2781B0"),
color: "#ffffff",
},
"&.xs-active": {
background: "#eaeaea",
},
}));
const ListContainer = styled.div(({ theme }) => ({
border: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
borderTop: "0px",
marginBottom: "45px",
"&::-webkit-scrollbar": {
width: "5px",
height: "5px",
},
"&::-webkit-scrollbar-track": {
background: "#F0F0F0",
borderRadius: 0,
boxShadow: "inset 0px 0px 0px 0px #F0F0F0",
},
"&::-webkit-scrollbar-thumb": {
background: "#777474",
borderRadius: 0,
},
"&::-webkit-scrollbar-thumb:hover": {
background: "#5A6375",
},
}));
const PlanHeader = ({
isActive,
isXsViewActive,
@@ -61,7 +309,7 @@ const PlanHeader = ({
}) => {
const plan = title.toLowerCase();
return (
<Box
<PlanHeaderContainer
className={clsx({
"plan-header": true,
active: isActive,
@@ -70,102 +318,9 @@ const PlanHeader = ({
onClick={() => {
onClick && onClick(plan);
}}
sx={{
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
flexFlow: "column",
borderLeft: "1px solid #eaeaea",
borderBottom: "0px !important",
"& .plan-header": {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexFlow: "column",
},
"& .title-block": {
display: "flex",
alignItems: "center",
flexFlow: "column",
width: "100%",
"& .title-main": {
display: "flex",
alignItems: "center",
justifyContent: "center",
flex: 1,
},
"& .iconContainer": {
"& .min-icon": {
minWidth: 140,
width: "100%",
maxHeight: 55,
height: "100%",
},
},
},
"& .open-source": {
fontSize: "14px",
display: "flex",
marginBottom: "5px",
alignItems: "center",
"& .min-icon": {
marginRight: "8px",
height: "12px",
width: "12px",
},
},
"& .cur-plan-text": {
fontSize: "12px",
textTransform: "uppercase",
},
"@media (max-width: 600px)": {
cursor: "pointer",
"& .title-block": {
"& .title": {
fontSize: "14px",
fontWeight: 600,
},
},
},
"&.active, &.active.xs-active": {
color: "#ffffff",
position: "relative",
"& .min-icon": {
fill: "#ffffff",
},
"&:before": {
content: "' '",
position: "absolute",
width: "100%",
height: "18px",
backgroundColor: "#2781B0",
display: "block",
top: -16,
},
"& .iconContainer": {
"& .min-icon": {
marginTop: "-12px",
},
},
},
"&.active": {
background: "#2781B0",
color: "#ffffff",
},
"&.xs-active": {
background: "#eaeaea",
},
}}
>
{children}
</Box>
</PlanHeaderContainer>
);
};
@@ -344,29 +499,7 @@ const LicensePlans = ({ licenseInfo }: IRegisterStatus) => {
const featureList = FEATURE_ITEMS;
return (
<Fragment>
<Box
sx={{
border: "1px solid #eaeaea",
borderTop: "0px",
marginBottom: "45px",
"&::-webkit-scrollbar": {
width: "5px",
height: "5px",
},
"&::-webkit-scrollbar-track": {
background: "#F0F0F0",
borderRadius: 0,
boxShadow: "inset 0px 0px 0px 0px #F0F0F0",
},
"&::-webkit-scrollbar-thumb": {
background: "#777474",
borderRadius: 0,
},
"&::-webkit-scrollbar-thumb:hover": {
background: "#5A6375",
},
}}
>
<ListContainer>
<Box
className={"title-blue-bar"}
sx={{
@@ -374,132 +507,7 @@ const LicensePlans = ({ licenseInfo }: IRegisterStatus) => {
borderBottom: "8px solid rgb(6 48 83)",
}}
/>
<Box
className={isPaidPlan ? "paid-plans-only" : ""}
sx={{
display: "grid",
margin: "0 1.5rem 0 1.5rem",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
[`@media (max-width: ${breakPoints.sm}px)`]: {
gridTemplateColumns: "1fr 1fr 1fr",
},
"&.paid-plans-only": {
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
},
"& .features-col": {
flex: 1,
minWidth: "260px",
"@media (max-width: 600px)": {
display: "none",
},
},
"& .xs-only": {
display: "none",
},
"& .button-box": {
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "5px 0px 25px 0px",
borderLeft: "1px solid #eaeaea",
},
"& .plan-header": {
height: "99px",
borderBottom: "1px solid #eaeaea",
},
"& .feature-title": {
height: "25px",
paddingLeft: "26px",
fontSize: "14px",
background: "#E5E5E5",
"@media (max-width: 600px)": {
"& .feature-title-info .xs-only": {
display: "block",
},
},
},
"& .feature-name": {
minHeight: "60px",
padding: "5px",
borderBottom: "1px solid #eaeaea",
display: "flex",
alignItems: "center",
paddingLeft: "26px",
fontSize: "14px",
},
"& .feature-item": {
display: "flex",
flexFlow: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "60px",
padding: "0 15px 0 15px",
borderBottom: "1px solid #eaeaea",
borderLeft: " 1px solid #eaeaea",
fontSize: "14px",
"& .link-text": {
color: "#2781B0",
cursor: "pointer",
textDecoration: "underline",
},
"&.icon-yes": {
width: "15px",
height: "15px",
},
},
"& .feature-item-info": {
flex: 1,
display: "flex",
flexFlow: "column",
alignItems: "center",
justifyContent: "space-around",
textAlign: "center",
"@media (max-width: 600px)": {
justifyContent: "space-evenly",
width: "100%",
"& .xs-only": {
display: "block",
},
"& .plan-feature": {
textAlign: "center",
paddingRight: "10px",
},
},
},
"& .plan-col": {
minWidth: "260px",
flex: 1,
},
"& .active-plan-col": {
background: "#FDFDFD 0% 0% no-repeat padding-box",
boxShadow: " 0px 3px 20px #00000038",
"& .plan-header": {
backgroundColor: "#2781B0",
},
"& .feature-title": {
background: "#F7F7F7",
},
},
}}
>
<PlanListContainer className={isPaidPlan ? "paid-plans-only" : ""}>
<Box className="features-col">
{featureList.map((fi) => {
const featureTitleRow = fi.featureTitleRow;
@@ -732,8 +740,8 @@ const LicensePlans = ({ licenseInfo }: IRegisterStatus) => {
)}
</Box>
</Box>
</Box>
</Box>
</PlanListContainer>
</ListContainer>
</Fragment>
);
};

View File

@@ -16,12 +16,17 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { AppState } from "../../store";
import { setErrorSnackMessage, userLogged } from "../../systemSlice";
import {
setDarkMode,
setErrorSnackMessage,
userLogged,
} from "../../systemSlice";
import { setNavigateTo } from "./loginSlice";
import { getTargetPath } from "./Login";
import { api } from "api";
import { ApiError, LoginRequest } from "api/consoleApi";
import { errorToHandler } from "api/errors";
import { isDarkModeOn } from "../../utils/stylesUtils";
export const doLoginAsync = createAsyncThunk(
"login/doLoginAsync",
@@ -47,10 +52,13 @@ export const doLoginAsync = createAsyncThunk(
return api.login
.login(payload)
.then((res) => {
const darkModeEnabled = isDarkModeOn(); // If null, then we set the dark mode as disabled per requirement. If configuration al ready set, then we establish this configuration
// We set the state in redux
dispatch(userLogged(true));
localStorage.setItem("userLoggedIn", accessKey);
dispatch(setNavigateTo(getTargetPath()));
dispatch(setDarkMode(!!darkModeEnabled));
})
.catch(async (res) => {
const err = (await res.json()) as ApiError;

View File

@@ -18,6 +18,7 @@ import { snackBarMessage, SRInfoStateType } from "./types";
import { ErrorResponseHandler, IEmbeddedCustomStyles } from "./common/types";
import { AppState } from "./store";
import { SubnetInfo } from "./screens/Console/License/types";
import { isDarkModeOn } from "./utils/stylesUtils";
// determine whether we have the sidebar state stored on localstorage
const initSideBarOpen = localStorage.getItem("sidebarOpen")
@@ -45,6 +46,7 @@ export interface SystemState {
helpName: string;
helpTabName: string;
locationPath: string;
darkMode: boolean;
}
const initialState: SystemState = {
@@ -76,6 +78,7 @@ const initialState: SystemState = {
helpName: "help",
helpTabName: "docs",
locationPath: "",
darkMode: isDarkModeOn(),
};
export const systemSlice = createSlice({
@@ -174,6 +177,9 @@ export const systemSlice = createSlice({
setLocationPath: (state, action: PayloadAction<string>) => {
state.locationPath = action.payload;
},
setDarkMode: (state, action: PayloadAction<boolean>) => {
state.darkMode = action.payload;
},
resetSystem: () => {
return initialState;
},
@@ -203,6 +209,7 @@ export const {
setHelpName,
setHelpTabName,
setLocationPath,
setDarkMode,
} = systemSlice.actions;
export const selDistSet = (state: AppState) => state.system.distributedSetup;

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { IEmbeddedCustomStyles } from "../common/types";
import get from "lodash/get";
export const getOverrideColorVariants: (
customStyles: string,
@@ -279,3 +280,18 @@ export const generateOverrideTheme = (overrideVars: IEmbeddedCustomStyles) => {
return retVal;
};
export const isDarkModeOn = () => {
const darkMode = localStorage.getItem("dark-mode");
if (!darkMode) {
const systemDarkMode = window.matchMedia("(prefers-color-scheme: dark)");
return get(systemDarkMode, "matches", false);
}
return darkMode === "on";
};
export const storeDarkMode = (mode: "on" | "off") => {
localStorage.setItem("dark-mode", mode);
};

View File

@@ -8038,9 +8038,9 @@ mdn-data@2.0.4:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
"mds@https://github.com/minio/mds.git#v0.12.1":
version "0.12.1"
resolved "https://github.com/minio/mds.git#4c4b18d022eed7c120a044709e677b25bc4500d7"
"mds@https://github.com/minio/mds.git#v0.12.2":
version "0.12.2"
resolved "https://github.com/minio/mds.git#82b14ea9544079a24db31e5cff477e3fc56c4e39"
dependencies:
"@types/styled-components" "^5.1.30"
"@uiw/react-textarea-code-editor" "^2.1.9"