Skip to content
Draft
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
1 change: 1 addition & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const config: StorybookConfig = {
"@storybook/addon-essentials",
"@storybook/addon-links",
"@storybook/addon-a11y",
"@storybook/addon-themes"
],
};

Expand Down
47 changes: 35 additions & 12 deletions frontend/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Global styles={GlobalStyles} />
<Story {...context} />
</>
);
};

export const decorators = [
(Story) => (
<Theme variant="light">
<Story />
</Theme>
),
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"],
},
],
},
Expand Down
25 changes: 25 additions & 0 deletions frontend/.storybook/withClutchTheme.decorator.js
Original file line number Diff line number Diff line change
@@ -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 (
<ThemeProvider variant={themes[selected]}>
<Story {...context} />
</ThemeProvider>
);
};
};
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion frontend/packages/core/src/AppLayout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -115,7 +116,11 @@ const Header: React.FC<HeaderProps> = ({
</>
)}
{children && children}
{userInfo && <UserInformation />}
{userInfo && (
<UserInformation>
<ThemeSwitcher />
</UserInformation>
)}
</Grid>
</Toolbar>
</AppBar>
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/core/src/AppLayout/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = () => (
Expand Down
48 changes: 48 additions & 0 deletions frontend/packages/core/src/AppLayout/theme-switcher.tsx
Original file line number Diff line number Diff line change
@@ -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: <LightModeIcon />,
},
{
label: THEME_VARIANTS.dark,
startAdornment: <DarkModeIcon />,
},
];

const handleOnChange = (value: string) => {
dispatch({
type: "SetPref",
payload: { key: "theme", value },
});
};

return (
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>Theme</Grid>
<Grid item>
<Select
name="theme-switcher"
label=""
options={options}
onChange={handleOnChange}
defaultOption={get(preferences, "theme")}
/>
</Grid>
</Grid>
);
};

export default ThemeSwitcher;
16 changes: 8 additions & 8 deletions frontend/packages/core/src/AppProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ const ClutchApp = ({

return (
<Router>
<Theme>
<div id="App">
<ApplicationContext.Provider value={appContextValue}>
<UserPreferencesProvider>
<UserPreferencesProvider>
<Theme>
<div id="App">
<ApplicationContext.Provider value={appContextValue}>
<ShortLinkContext.Provider value={shortLinkProviderProps}>
{hydrateError && (
<Toast onClose={() => setHydrateError(null)}>
Expand Down Expand Up @@ -238,10 +238,10 @@ const ClutchApp = ({
</Route>
</Routes>
</ShortLinkContext.Provider>
</UserPreferencesProvider>
</ApplicationContext.Provider>
</div>
</Theme>
</ApplicationContext.Provider>
</div>
</Theme>
</UserPreferencesProvider>
</Router>
);
};
Expand Down
27 changes: 17 additions & 10 deletions frontend/packages/core/src/AppProvider/themes.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 (
<ThemeProvider variant={prefersDarkMode ? THEME_VARIANTS.dark : THEME_VARIANTS.light}>
{children}
</ThemeProvider>
);
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 <ThemeProvider variant={variant}>{children}</ThemeProvider>;
};

export { Theme, useTheme };
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
4 changes: 2 additions & 2 deletions frontend/packages/core/src/horizontal-rule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/core/src/stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
10 changes: 10 additions & 0 deletions frontend/yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.