Skip to content

Commit d2d8a90

Browse files
committed
feat(theme): implement dark mode toggle and update theme styles across components
1 parent d5451a8 commit d2d8a90

File tree

6 files changed

+105
-46
lines changed

6 files changed

+105
-46
lines changed

crossbar_llm/frontend/src/App.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { useState, useEffect } from 'react';
22
import { ThemeProvider, CssBaseline, Box, Container, Tabs, Tab, Grid2, Modal, Typography, Button, IconButton } from '@mui/material';
33
import CloseIcon from '@mui/icons-material/Close';
4-
import theme from './theme';
4+
import Brightness4Icon from '@mui/icons-material/Brightness4';
5+
import Brightness7Icon from '@mui/icons-material/Brightness7';
6+
import { getTheme } from './theme';
57
import QueryInput from './components/QueryInput';
68
import ResultsDisplay from './components/ResultsDisplay';
79
import About from './components/About';
@@ -19,6 +21,26 @@ function App() {
1921
const [provider, setProvider] = useState('');
2022
const [llmType, setLlmType] = useState('');
2123
const [apiKey, setApiKey] = useState('');
24+
const [mode, setMode] = useState(() => {
25+
const savedMode = localStorage.getItem('theme-mode');
26+
if (savedMode) {
27+
return savedMode;
28+
}
29+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
30+
});
31+
32+
const theme = React.useMemo(() => getTheme(mode), [mode]);
33+
34+
useEffect(() => {
35+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
36+
const handleChange = (e) => {
37+
if (!localStorage.getItem('theme-mode')) {
38+
setMode(e.matches ? 'dark' : 'light');
39+
}
40+
};
41+
mediaQuery.addListener(handleChange);
42+
return () => mediaQuery.removeListener(handleChange);
43+
}, []);
2244

2345
useEffect(() => {
2446
axios.get('/csrf-token/', { withCredentials: true })
@@ -60,10 +82,27 @@ function App() {
6082
setSelectedQuery(query);
6183
}
6284

85+
const toggleColorMode = () => {
86+
const newMode = mode === 'light' ? 'dark' : 'light';
87+
setMode(newMode);
88+
localStorage.setItem('theme-mode', newMode);
89+
};
90+
6391
return (
6492
<ThemeProvider theme={theme}>
6593
<CssBaseline />
66-
<Box sx={{ minHeight: '100vh', backgroundColor: 'background.default', mt: { xs: 4, sm: 6, md: 10 }, mb: { xs: 4, sm: 6, md: 10 } }}>
94+
<Box sx={{ minHeight: '100vh', backgroundColor: 'background.default', mt: { xs: 4, sm: 6, md: 10 }, mb: { xs: 4, sm: 6, md: 10 }, position: 'relative' }}>
95+
<IconButton
96+
onClick={toggleColorMode}
97+
sx={{
98+
position: 'absolute',
99+
right: 16,
100+
top: 16,
101+
color: 'text.primary'
102+
}}
103+
>
104+
{mode === 'dark' ? <Brightness7Icon /> : <Brightness4Icon />}
105+
</IconButton>
67106
<Container maxWidth="lg" sx={{ px: { xs: 2, sm: 3, md: 4 } }}>
68107
<Typography
69108
variant="h2"

crossbar_llm/frontend/src/components/LatestQueries.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ function LatestQueries({ queries, onSelectQuery }) {
4545
onRowClick={handleRowClick}
4646
rowsPerPageOptions={[5]}
4747
disableSelectionOnClick
48+
getRowHeight={() => 'auto'}
49+
sx={{ '& .MuiDataGrid-cell': { borderRight: '1px solid rgba(0, 0, 0, 0.12)' } }}
4850
/>
4951
</div>
5052
);

crossbar_llm/frontend/src/components/QueryInput.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
FormControlLabel,
1212
Checkbox,
1313
CircularProgress,
14+
useTheme,
1415
} from '@mui/material';
1516
import AutocompleteTextField from './AutocompleteTextField';
1617
import axios from '../services/api';
@@ -38,7 +39,7 @@ function QueryInput({
3839
const [showWarning, setShowWarning] = useState(false);
3940
const [realtimeLogs, setRealtimeLogs] = useState('');
4041
const eventSourceRef = useRef(null);
41-
42+
const theme = useTheme();
4243

4344
const modelChoices = {
4445
OpenAI: [
@@ -425,20 +426,21 @@ function QueryInput({
425426
{logs && (
426427
<Box
427428
sx={{
428-
backgroundColor: '#2d2d2d',
429+
backgroundColor: theme.palette.mode === 'dark' ? '#1e1e1e' : '#f5f5f5',
429430
padding: 2,
430431
borderRadius: 1,
431432
overflow: 'auto',
432433
maxHeight: 200,
433434
mt: 2,
435+
border: `1px solid ${theme.palette.divider}`
434436
}}
435437
>
436438
<pre
437439
style={{
438440
margin: 0,
439441
fontFamily: 'monospace',
440442
fontSize: 12,
441-
color: '#cccccc',
443+
color: theme.palette.text.primary,
442444
}}
443445
>
444446
{logs}

crossbar_llm/frontend/src/components/ResultsDisplay.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from 'react';
2-
import { Typography, Card, CardContent, Box } from '@mui/material';
2+
import { Typography, Card, CardContent, Box, useTheme } from '@mui/material';
33
import SyntaxHighlighter from 'react-syntax-highlighter';
4-
import { dracula } from 'react-syntax-highlighter/dist/esm/styles/hljs';
4+
import { docco, dracula } from 'react-syntax-highlighter/dist/esm/styles/hljs';
55

66
function ResultsDisplay({ queryResult, executionResult, realtimeLogs }) {
7+
const theme = useTheme();
8+
const syntaxTheme = theme.palette.mode === 'dark' ? dracula : docco;
9+
710
if (!queryResult && !executionResult) {
811
return null;
912
}
@@ -14,7 +17,13 @@ function ResultsDisplay({ queryResult, executionResult, realtimeLogs }) {
1417
<Card sx={{ mb: 2 }}>
1518
<CardContent>
1619
<Typography variant="h6">Generated Cypher Query:</Typography>
17-
<SyntaxHighlighter language="cypher" style={dracula}>
20+
<SyntaxHighlighter
21+
language="cypher"
22+
style={syntaxTheme}
23+
customStyle={{
24+
backgroundColor: theme.palette.mode === 'dark' ? '#1e1e1e' : '#f5f5f5'
25+
}}
26+
>
1827
{queryResult}
1928
</SyntaxHighlighter>
2029
</CardContent>
@@ -25,14 +34,20 @@ function ResultsDisplay({ queryResult, executionResult, realtimeLogs }) {
2534
<CardContent>
2635
<Typography variant="h6">Results:</Typography>
2736
{realtimeLogs && (
28-
<Card sx={{ mb: 2 }}>
29-
<CardContent>
30-
<Typography variant="h6">Real-time Logs:</Typography>
31-
<SyntaxHighlighter language="plaintext" style={dracula}>
32-
{realtimeLogs}
33-
</SyntaxHighlighter>
34-
</CardContent>
35-
</Card>
37+
<Card sx={{ mb: 2 }}>
38+
<CardContent>
39+
<Typography variant="h6">Real-time Logs:</Typography>
40+
<SyntaxHighlighter
41+
language="plaintext"
42+
style={syntaxTheme}
43+
customStyle={{
44+
backgroundColor: theme.palette.mode === 'dark' ? '#1e1e1e' : '#f5f5f5'
45+
}}
46+
>
47+
{realtimeLogs}
48+
</SyntaxHighlighter>
49+
</CardContent>
50+
</Card>
3651
)}
3752
<Typography variant="body1" sx={{ mt: 2, fontWeight: 'bold' }}>
3853
Natural Language Response:
@@ -44,7 +59,13 @@ function ResultsDisplay({ queryResult, executionResult, realtimeLogs }) {
4459
<Typography variant="subtitle1" sx={{ mt: 2 }}>
4560
Raw Query Output:
4661
</Typography>
47-
<SyntaxHighlighter language="json" style={dracula}>
62+
<SyntaxHighlighter
63+
language="json"
64+
style={syntaxTheme}
65+
customStyle={{
66+
backgroundColor: theme.palette.mode === 'dark' ? '#1e1e1e' : '#f5f5f5'
67+
}}
68+
>
4869
{JSON.stringify(executionResult.result, null, 2)}
4970
</SyntaxHighlighter>
5071
</CardContent>

crossbar_llm/frontend/src/index.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import React from 'react';
2-
import ReactDOM from 'react-dom';
2+
import ReactDOM from 'react-dom/client';
33
import App from './App';
4-
import theme from './theme';
5-
import { ThemeProvider, CssBaseline } from '@mui/material';
4+
import './index.css';
65

76
const root = ReactDOM.createRoot(document.getElementById('root'));
87
root.render(
9-
<ThemeProvider theme={theme}>
10-
<CssBaseline />
8+
<React.StrictMode>
119
<App />
12-
</ThemeProvider>,
13-
document.getElementById('root')
10+
</React.StrictMode>
1411
);

crossbar_llm/frontend/src/theme.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,53 @@
11
import { createTheme } from '@mui/material/styles';
22

3-
const theme = createTheme({
3+
export const getTheme = (mode) => createTheme({
44
palette: {
5-
mode: 'light',
5+
mode,
66
primary: {
7-
main: '#1976d2',
7+
main: '#1976d2',
88
},
99
secondary: {
10-
main: '#ffffff',
10+
main: mode === 'dark' ? '#333333' : '#ffffff',
1111
},
1212
background: {
13-
default: '#f5f5f5',
14-
paper: '#ffffff',
13+
default: mode === 'dark' ? '#121212' : '#f5f5f5',
14+
paper: mode === 'dark' ? '#1e1e1e' : '#ffffff',
1515
},
1616
text: {
17-
primary: '#000000',
18-
secondary: '#555555',
17+
primary: mode === 'dark' ? '#ffffff' : '#000000',
18+
secondary: mode === 'dark' ? '#a0a0a0' : '#555555',
1919
},
2020
},
2121
typography: {
22-
fontFamily: 'Inter, sans-serif',
22+
fontFamily: 'Inter, sans-serif',
2323
h5: {
24-
fontWeight: 600,
24+
fontWeight: 600,
2525
},
2626
subtitle1: {
27-
color: '#888888',
27+
color: mode === 'dark' ? '#888888' : '#666666',
2828
},
2929
},
3030
components: {
3131
MuiButton: {
3232
styleOverrides: {
3333
root: {
34-
borderRadius: '8px',
35-
textTransform: 'none',
36-
boxShadow: 'none',
34+
borderRadius: '8px',
35+
textTransform: 'none',
36+
boxShadow: 'none',
3737
},
3838
containedPrimary: {
39-
backgroundColor: '#000000',
40-
color: '#ffffff',
39+
backgroundColor: mode === 'dark' ? '#1976d2' : '#000000',
40+
color: '#ffffff',
4141
'&:hover': {
42-
backgroundColor: '#333333',
42+
backgroundColor: mode === 'dark' ? '#1565c0' : '#333333',
4343
},
4444
},
4545
},
4646
},
4747
MuiTextField: {
4848
styleOverrides: {
4949
root: {
50-
backgroundColor: '#ffffff',
50+
backgroundColor: mode === 'dark' ? '#1e1e1e' : '#ffffff',
5151
borderRadius: '8px',
5252
},
5353
},
@@ -59,12 +59,10 @@ const theme = createTheme({
5959
MuiSelect: {
6060
styleOverrides: {
6161
select: {
62-
backgroundColor: '#ffffff',
62+
backgroundColor: mode === 'dark' ? '#1e1e1e' : '#ffffff',
6363
borderRadius: '8px',
6464
},
6565
},
6666
},
6767
},
68-
});
69-
70-
export default theme;
68+
});

0 commit comments

Comments
 (0)