Skip to content

Commit

Permalink
Merge pull request #5 from gorules/feat/dark-mode
Browse files Browse the repository at this point in the history
feat: add dark mode
  • Loading branch information
stefan-gorules authored Jan 31, 2024
2 parents 3919803 + 1609cc0 commit 1d4a290
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ module.exports = {
rules: {
'prettier/prettier': 'error',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react-hooks/exhaustive-deps': 'off'
'react-hooks/exhaustive-deps': 'off',
},
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --fix",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
"lint:fix": "eslint . --ext ts,tsx --fix",
"typecheck": "tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@gorules/jdm-editor": "1.0.0-beta.3",
"@gorules/jdm-editor": "1.0.0-beta.5",
"ace-builds": "^1.31.2",
"antd": "^5.11.5",
"axios": "^1.6.2",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

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

69 changes: 69 additions & 0 deletions src/context/theme.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { JdmConfigProvider } from '@gorules/jdm-editor';
import { ConfigProvider, theme } from 'antd';
import { match } from 'ts-pattern';

const colorMediaQuery = () => window.matchMedia('(prefers-color-scheme: dark)');

export enum ThemePreference {

Check warning on line 8 in src/context/theme.provider.tsx

View workflow job for this annotation

GitHub Actions / Code quality

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
Automatic = 'automatic',
Dark = 'dark',
Light = 'light',
}

type ThemeContextState = {
themePreference: ThemePreference;
setThemePreference: (preference: ThemePreference) => void;
};

// eslint-disable-next-line
export const ThemeContext = createContext<ThemeContextState>({} as any);

export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [themePreference, setThemePreferenceInternal] = useState<ThemePreference>(() => {
return match(localStorage.getItem('themePreference'))
.with('dark', () => ThemePreference.Dark)
.with('light', () => ThemePreference.Light)
.otherwise(() => ThemePreference.Automatic);
});

const [isAutomaticDark, setIsAutomaticDark] = useState(() => colorMediaQuery().matches);

const isDarkTheme = useMemo<boolean>(() => {
return match(themePreference)
.with(ThemePreference.Dark, () => true)
.with(ThemePreference.Light, () => false)
.otherwise(() => isAutomaticDark);
}, [themePreference, isAutomaticDark]);

useEffect(() => {
const eventTarget = colorMediaQuery();
const listener = (event: MediaQueryListEvent) => {
setIsAutomaticDark(event.matches);
};

eventTarget.addEventListener('change', listener);
return () => {
eventTarget.removeEventListener('change', listener);
};
}, []);

useEffect(() => {
document.body.setAttribute('data-theme', isDarkTheme ? 'dark' : 'light');
}, [isDarkTheme]);

const setThemePreference = (preference: ThemePreference) => {
setThemePreferenceInternal(preference);
localStorage.setItem('themePreference', preference);
};

return (
<ConfigProvider theme={{ algorithm: isDarkTheme ? theme.darkAlgorithm : theme.defaultAlgorithm }}>
<ThemeContext.Provider value={{ themePreference, setThemePreference }}>
<JdmConfigProvider theme={{ mode: isDarkTheme ? 'dark' : 'light' }}>{children}</JdmConfigProvider>
</ThemeContext.Provider>
</ConfigProvider>
);
};

export const useTheme = () => useContext(ThemeContext);

Check warning on line 69 in src/context/theme.provider.tsx

View workflow job for this annotation

GitHub Actions / Code quality

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
8 changes: 8 additions & 0 deletions src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ body {

.grl-dn__header__icon {
box-sizing: content-box;
}

body[data-theme="dark"] {
background: black;
}

body[data-theme="light"] {
background: white;
}
6 changes: 3 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import 'ace-builds/src-noconflict/snippets/javascript';
import 'ace-builds/src-noconflict/theme-chrome';

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { JdmConfigProvider } from '@gorules/jdm-editor';
import { DecisionSimplePage } from './pages/decision-simple.tsx';
import { NotFoundPage } from './pages/not-found';
import { ThemeContextProvider } from './context/theme.provider.tsx';

const router = createBrowserRouter([
{
Expand All @@ -33,8 +33,8 @@ const router = createBrowserRouter([

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<JdmConfigProvider>
<ThemeContextProvider>
<RouterProvider router={router} />
</JdmConfigProvider>
</ThemeContextProvider>
</React.StrictMode>,
);
1 change: 0 additions & 1 deletion src/pages/decision-simple.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
overflow-y: auto;
display: flex;
gap: 8px;
max-height: calc(100vh - 80px);
}

.content {
Expand Down
72 changes: 56 additions & 16 deletions src/pages/decision-simple.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { Button, Dropdown, message, Modal, Typography } from 'antd';
import { PlayCircleOutlined } from '@ant-design/icons';
import { Button, Dropdown, message, Modal, theme, Typography } from 'antd';
import { BulbOutlined, CheckOutlined, PlayCircleOutlined } from '@ant-design/icons';
import { decisionTemplates } from '../assets/decision-templates';
import { displayError } from '../helpers/error-message.ts';
import { DecisionContent, DecisionEdge, DecisionNode } from '../helpers/graph.ts';
Expand All @@ -14,6 +14,7 @@ import { match, P } from 'ts-pattern';

import classes from './decision-simple.module.css';
import axios from 'axios';
import { ThemePreference, useTheme } from '../context/theme.provider.tsx';

enum DocumentFileTypes {
Decision = 'application/vnd.gorules.decision',
Expand All @@ -22,13 +23,14 @@ enum DocumentFileTypes {
const supportFSApi = Object.hasOwn(window, 'showSaveFilePicker');

export const DecisionSimplePage: React.FC = () => {
const { token } = theme.useToken();
const fileInput = useRef<HTMLInputElement>(null);
const graphRef = React.useRef<DecisionGraphRef>(null);
const { themePreference, setThemePreference } = useTheme();

const [searchParams] = useSearchParams();
const [fileHandle, setFileHandle] = useState<FileSystemFileHandle>();
const [simulatorOpened, setSimulatorOpened] = useState(false);
const [editGraph, setEditGraph] = useState(false);
const [graph, setGraph] = useState<DecisionContent>({ nodes: [], edges: [] });
const [fileName, setFileName] = useState('Name');

Expand Down Expand Up @@ -240,7 +242,12 @@ export const DecisionSimplePage: React.FC = () => {
/>
<div className={classes.page}>
<PageHeader
style={{ padding: '8px 16px', background: 'white', marginBottom: 8, boxSizing: 'border-box' }}
style={{
padding: '8px 16px',
background: token.colorBgLayout,
boxSizing: 'border-box',
borderBottom: `1px solid ${token.colorBorder}`,
}}
title={
<div className={classes.heading}>
<div className={classes.logo}>
Expand Down Expand Up @@ -308,16 +315,51 @@ export const DecisionSimplePage: React.FC = () => {
}
ghost={false}
extra={[
!editGraph && (
<Button
type={simulatorOpened ? 'primary' : 'default'}
ghost={simulatorOpened}
icon={<PlayCircleOutlined />}
onClick={() => graphRef.current?.toggleSimulator()}
>
{simulatorOpened ? 'Close' : 'Open'} Simulator
</Button>
),
<Button
type={simulatorOpened ? 'primary' : 'default'}
ghost={simulatorOpened}
icon={<PlayCircleOutlined />}
onClick={() => graphRef.current?.toggleSimulator()}
>
{simulatorOpened ? 'Close' : 'Open'} Simulator
</Button>,
<Dropdown
overlayStyle={{ minWidth: 150 }}
menu={{
onClick: ({ key }) => setThemePreference(key as ThemePreference),
items: [
{
label: 'Automatic',
key: ThemePreference.Automatic,
icon: (
<CheckOutlined
style={{ visibility: themePreference === ThemePreference.Automatic ? 'visible' : 'hidden' }}
/>
),
},
{
label: 'Dark',
key: ThemePreference.Dark,
icon: (
<CheckOutlined
style={{ visibility: themePreference === ThemePreference.Dark ? 'visible' : 'hidden' }}
/>
),
},
{
label: 'Light',
key: ThemePreference.Light,
icon: (
<CheckOutlined
style={{ visibility: themePreference === ThemePreference.Light ? 'visible' : 'hidden' }}
/>
),
},
],
}}
>
<Button type="text" icon={<BulbOutlined />} />
</Dropdown>,
]}
/>
<div className={classes.contentWrapper}>
Expand All @@ -326,7 +368,6 @@ export const DecisionSimplePage: React.FC = () => {
ref={graphRef}
value={graph}
onChange={(value) => setGraph(value)}
onEditGraph={(val) => setEditGraph(val || false)}
reactFlowProOptions={{ hideAttribution: true }}
onSimulatorOpen={setSimulatorOpened}
onSimulationRun={async ({ decisionGraph, context }) => {
Expand All @@ -342,7 +383,6 @@ export const DecisionSimplePage: React.FC = () => {
return { error: e };
}
}}
hideExportImport
/>
</div>
</div>
Expand Down

0 comments on commit 1d4a290

Please sign in to comment.