diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts
index 6762bfb8a7..d6ff1f75f8 100644
--- a/frontend/.storybook/main.ts
+++ b/frontend/.storybook/main.ts
@@ -45,6 +45,7 @@ const config: StorybookConfig = {
"@storybook/addon-essentials",
"@storybook/addon-links",
"@storybook/addon-a11y",
+ "@storybook/addon-themes"
],
};
diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js
index 6e534887d7..f97832a553 100644
--- a/frontend/.storybook/preview.js
+++ b/frontend/.storybook/preview.js
@@ -1,25 +1,48 @@
-import React from "react";
-import { Theme } from "./../packages/core/src/AppProvider/themes";
+import { DecoratorHelpers } from "@storybook/addon-themes";
+import { withClutchTheme } from "./withClutchTheme.decorator";
+import { Global, css } from "@emotion/react";
+import { clutchColors } from "../packages/core/src/Theme/colors";
+
+const { pluckThemeFromContext } = DecoratorHelpers;
+
+const withGlobalStyles = (Story, context) => {
+ const selectedTheme = pluckThemeFromContext(context);
+ const GlobalStyles = css`
+ body {
+ background: ${clutchColors(selectedTheme).neutral["50"]};
+ }
+ `;
+
+ return (
+ <>
+
+
+ >
+ );
+};
export const decorators = [
- (Story) => (
-
-
-
- ),
+ withClutchTheme({
+ themes: {
+ light: "light",
+ dark: "dark",
+ },
+ defaultTheme: "light",
+ }),
+ withGlobalStyles,
];
export const parameters = {
backgrounds: {
- default: "clutch",
+ default: "light",
values: [
{
- name: "clutch",
- value: "#f9fafe",
+ name: "light",
+ value: clutchColors("light").neutral["50"],
},
{
- name: "light",
- value: "#ffffff",
+ name: "dark",
+ value: clutchColors("dark").neutral["50"],
},
],
},
diff --git a/frontend/.storybook/withClutchTheme.decorator.js b/frontend/.storybook/withClutchTheme.decorator.js
new file mode 100644
index 0000000000..e30b6f8137
--- /dev/null
+++ b/frontend/.storybook/withClutchTheme.decorator.js
@@ -0,0 +1,25 @@
+import { DecoratorHelpers } from "@storybook/addon-themes";
+import { ThemeProvider } from "../packages/core/src/Theme";
+
+const {
+ initializeThemeState,
+ pluckThemeFromContext,
+ useThemeParameters,
+} = DecoratorHelpers;
+
+export const withClutchTheme = ({ themes, defaultTheme }) => {
+ initializeThemeState(Object.keys(themes), defaultTheme);
+
+ return (Story, context) => {
+ const selectedTheme = pluckThemeFromContext(context);
+ const { themeOverride } = useThemeParameters();
+
+ const selected = themeOverride || selectedTheme || defaultTheme;
+
+ return (
+
+
+
+ );
+ };
+};
diff --git a/frontend/package.json b/frontend/package.json
index 95562f4ca5..983432d084 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -103,6 +103,7 @@
"@storybook/addon-actions": "^7.6.0",
"@storybook/addon-essentials": "^7.6.0",
"@storybook/addon-links": "^7.6.0",
+ "@storybook/addon-themes": "^7.6.17",
"@storybook/node-logger": "^7.6.0",
"@storybook/preset-typescript": "^3.0.0",
"@storybook/react-webpack5": "^7.6.0",
diff --git a/frontend/packages/core/src/AppLayout/header.tsx b/frontend/packages/core/src/AppLayout/header.tsx
index 6100e8f403..18bd8e955e 100644
--- a/frontend/packages/core/src/AppLayout/header.tsx
+++ b/frontend/packages/core/src/AppLayout/header.tsx
@@ -11,6 +11,7 @@ import Logo from "./logo";
import Notifications from "./notifications";
import SearchField from "./search";
import ShortLinker from "./shortLinker";
+import ThemeSwitcher from "./theme-switcher";
import { UserInformation } from "./user";
export const APP_BAR_HEIGHT = "64px";
@@ -115,7 +116,11 @@ const Header: React.FC = ({
>
)}
{children && children}
- {userInfo && }
+ {userInfo && (
+
+
+
+ )}
diff --git a/frontend/packages/core/src/AppLayout/search.tsx b/frontend/packages/core/src/AppLayout/search.tsx
index b1b6e110b0..139c938a0b 100644
--- a/frontend/packages/core/src/AppLayout/search.tsx
+++ b/frontend/packages/core/src/AppLayout/search.tsx
@@ -80,7 +80,7 @@ const ResultLabel = styled(Typography)(({ theme }: { theme: Theme }) => ({
// main search icon on header
const SearchIconButton = styled(IconButton)(({ theme }: { theme: Theme }) => ({
- color: theme.palette.contrastColor,
+ color: theme.palette.common.white,
fontSize: "24px",
padding: "12px",
marginRight: "8px",
diff --git a/frontend/packages/core/src/AppLayout/stories/searchfield.stories.tsx b/frontend/packages/core/src/AppLayout/stories/searchfield.stories.tsx
index c378d880b5..cdf7538fbd 100644
--- a/frontend/packages/core/src/AppLayout/stories/searchfield.stories.tsx
+++ b/frontend/packages/core/src/AppLayout/stories/searchfield.stories.tsx
@@ -5,6 +5,7 @@ import { Box, Grid as MuiGrid, Theme } from "@mui/material";
import type { Meta } from "@storybook/react";
import { ApplicationContext } from "../../Contexts/app-context";
+import { THEME_VARIANTS } from "../../Theme/colors";
import SearchFieldComponent from "../search";
export default {
@@ -62,7 +63,10 @@ export default {
const Grid = styled(MuiGrid)(({ theme }: { theme: Theme }) => ({
height: "64px",
- backgroundColor: theme.palette.primary[900],
+ backgroundColor:
+ theme.palette.mode === THEME_VARIANTS.light
+ ? theme.palette.primary[900]
+ : theme.palette.headerGradient,
}));
const Template = () => (
diff --git a/frontend/packages/core/src/AppLayout/theme-switcher.tsx b/frontend/packages/core/src/AppLayout/theme-switcher.tsx
new file mode 100644
index 0000000000..0806658f63
--- /dev/null
+++ b/frontend/packages/core/src/AppLayout/theme-switcher.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import DarkModeIcon from "@mui/icons-material/DarkMode";
+import LightModeIcon from "@mui/icons-material/LightMode";
+import { Grid } from "@mui/material";
+import get from "lodash/get";
+
+import { useUserPreferences } from "../Contexts";
+import { Select } from "../Input";
+import { THEME_VARIANTS } from "../Theme/colors";
+
+const ThemeSwitcher = () => {
+ const { preferences, dispatch } = useUserPreferences();
+
+ const options = [
+ {
+ label: THEME_VARIANTS.light,
+ startAdornment: ,
+ },
+ {
+ label: THEME_VARIANTS.dark,
+ startAdornment: ,
+ },
+ ];
+
+ const handleOnChange = (value: string) => {
+ dispatch({
+ type: "SetPref",
+ payload: { key: "theme", value },
+ });
+ };
+
+ return (
+
+ Theme
+
+
+
+
+ );
+};
+
+export default ThemeSwitcher;
diff --git a/frontend/packages/core/src/AppProvider/index.tsx b/frontend/packages/core/src/AppProvider/index.tsx
index 3ee75119ac..5c485883c8 100644
--- a/frontend/packages/core/src/AppProvider/index.tsx
+++ b/frontend/packages/core/src/AppProvider/index.tsx
@@ -160,10 +160,10 @@ const ClutchApp = ({
return (
-
-
-
-
+
+
+
+
{hydrateError && (
setHydrateError(null)}>
@@ -238,10 +238,10 @@ const ClutchApp = ({
-
-
-
-
+
+
+
+
);
};
diff --git a/frontend/packages/core/src/AppProvider/themes.tsx b/frontend/packages/core/src/AppProvider/themes.tsx
index 6944a85902..1a2b213fdf 100644
--- a/frontend/packages/core/src/AppProvider/themes.tsx
+++ b/frontend/packages/core/src/AppProvider/themes.tsx
@@ -1,10 +1,13 @@
import React from "react";
import { useTheme as useMuiTheme } from "@mui/material";
import type { Theme as MuiTheme } from "@mui/material/styles";
+import get from "lodash/get";
+import isEmpty from "lodash/isEmpty";
+import { useUserPreferences } from "../Contexts";
import { ThemeProvider } from "../Theme";
-import { THEME_VARIANTS } from "../Theme/colors";
import type { ClutchColors } from "../Theme/types";
+import { THEME_VARIANTS } from "../Theme/types";
declare module "@mui/material/styles" {
interface Theme {
@@ -23,16 +26,20 @@ declare module "@mui/material/styles" {
const useTheme = () => useMuiTheme() as MuiTheme;
const Theme: React.FC = ({ children }) => {
- // Uncomment to use dark mode
- /* // Detect system color mode
+ const { preferences } = useUserPreferences();
+
const prefersDarkMode =
- window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; */
- const prefersDarkMode = false;
- return (
-
- {children}
-
- );
+ window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
+
+ const themeVariant = get(preferences, "theme");
+
+ const variant = isEmpty(themeVariant)
+ ? prefersDarkMode
+ ? THEME_VARIANTS.dark
+ : THEME_VARIANTS.light
+ : themeVariant;
+
+ return {children};
};
export { Theme, useTheme };
diff --git a/frontend/packages/core/src/Contexts/preferences-context.tsx b/frontend/packages/core/src/Contexts/preferences-context.tsx
index b582b93ea6..13b9bca483 100644
--- a/frontend/packages/core/src/Contexts/preferences-context.tsx
+++ b/frontend/packages/core/src/Contexts/preferences-context.tsx
@@ -9,6 +9,7 @@ type Dispatch = (action: Action) => void;
type UserPreferencesProviderProps = { children: React.ReactNode };
const DEFAULT_PREFERENCES: State = {
timeFormat: "UTC",
+ theme: "",
} as any;
interface ContextProps {
preferences: State;
diff --git a/frontend/packages/core/src/Input/stories/date-time.stories.tsx b/frontend/packages/core/src/Input/stories/date-time.stories.tsx
index b9dcc8955c..be8b8ac5e3 100644
--- a/frontend/packages/core/src/Input/stories/date-time.stories.tsx
+++ b/frontend/packages/core/src/Input/stories/date-time.stories.tsx
@@ -1,5 +1,6 @@
import * as React from "react";
import type { Meta } from "@storybook/react";
+import dayjs from "dayjs";
import type { DateTimePickerProps } from "../date-time";
import DateTimePicker from "../date-time";
@@ -46,19 +47,19 @@ WithError.args = {
...PrimaryDemo.args,
error: true,
helperText: "error in the field",
- onChange: (newValue: unknown) => null,
+ onChange: (_newValue: unknown) => null,
} as DateTimePickerProps;
export const WithMinDate = Template.bind({});
WithMinDate.args = {
...PrimaryDemo.args,
- minDate: new Date(),
+ minDate: dayjs(new Date()),
onChange: (newValue: unknown) => null,
} as DateTimePickerProps;
export const WithMaxDate = Template.bind({});
WithMaxDate.args = {
...PrimaryDemo.args,
- maxDate: new Date(),
- onChange: (newValue: unknown) => null,
+ maxDate: dayjs(new Date()),
+ onChange: (_newValue: unknown) => null,
} as DateTimePickerProps;
diff --git a/frontend/packages/core/src/horizontal-rule.tsx b/frontend/packages/core/src/horizontal-rule.tsx
index d37b9962f8..945ec0ff63 100644
--- a/frontend/packages/core/src/horizontal-rule.tsx
+++ b/frontend/packages/core/src/horizontal-rule.tsx
@@ -32,14 +32,14 @@ const StyledHorizontalRule = styled(HorizontalRuleBase)(({ theme }: { theme: The
".line > span": {
display: "block",
- borderTop: `1px solid ${alpha(theme.palette.secondary[900], 0.12)}`,
+ borderTop: `1px solid ${alpha(theme.palette.secondary[900], 0.38)}`,
},
".content": {
padding: "0 16px",
fontWeight: "bold",
fontSize: "14px",
- color: alpha(theme.palette.secondary[900], 0.38),
+ color: alpha(theme.palette.secondary[900], 0.6),
textTransform: "uppercase",
display: "inline-flex",
alignItems: "center",
diff --git a/frontend/packages/core/src/stepper.tsx b/frontend/packages/core/src/stepper.tsx
index 8dc39ac7df..52d09ce299 100644
--- a/frontend/packages/core/src/stepper.tsx
+++ b/frontend/packages/core/src/stepper.tsx
@@ -56,7 +56,7 @@ const StepContainer = styled("div")<{ $orientation: StepperOrientation }>(
".MuiStepLabel-label": {
fontWeight: 500,
fontSize: "14px",
- color: alpha(theme.palette.secondary[900], 0.38),
+ color: alpha(theme.palette.secondary[900], 0.5),
},
".MuiStepLabel-label.Mui-active": {
color: theme.palette.secondary[900],
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 0353d671a2..20ac93e0dd 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -1895,6 +1895,7 @@ __metadata:
"@storybook/addon-actions": "npm:^7.6.0"
"@storybook/addon-essentials": "npm:^7.6.0"
"@storybook/addon-links": "npm:^7.6.0"
+ "@storybook/addon-themes": "npm:^7.6.17"
"@storybook/node-logger": "npm:^7.6.0"
"@storybook/preset-typescript": "npm:^3.0.0"
"@storybook/react-webpack5": "npm:^7.6.0"
@@ -4776,6 +4777,15 @@ __metadata:
languageName: node
linkType: hard
+"@storybook/addon-themes@npm:^7.6.17":
+ version: 7.6.19
+ resolution: "@storybook/addon-themes@npm:7.6.19"
+ dependencies:
+ ts-dedent: "npm:^2.0.0"
+ checksum: 10c0/c5932710df39b13cfd669febd8fa6d400fdff65466acc8afe857d49573da9d40e9515d8e0d3687846d50d128f76bf5ac84a37ef7634e2383671fbd9b6ab03483
+ languageName: node
+ linkType: hard
+
"@storybook/addon-toolbars@npm:7.6.17":
version: 7.6.17
resolution: "@storybook/addon-toolbars@npm:7.6.17"