11import * as React from 'react' ;
22import { Link as RouterLink , Route , Routes } from 'react-router-dom' ;
33
4- import { styled } from '@mui/material/styles' ;
4+ import { createTheme , styled , ThemeProvider } from '@mui/material/styles' ;
55import Link from '@mui/material/Link' ;
66import MuiDrawer from '@mui/material/Drawer' ;
77import Box from '@mui/material/Box' ;
@@ -37,6 +37,19 @@ import ReadTag from './pages/tags/Read';
3737import ReadUser from './pages/users/Read' ;
3838import { useCurrentUser } from './authentication' ;
3939import ReadRequest from './pages/requests/Read' ;
40+ import {
41+ alpha ,
42+ CssBaseline ,
43+ PaletteMode ,
44+ Stack ,
45+ ToggleButton ,
46+ ToggleButtonGroup ,
47+ Tooltip ,
48+ useMediaQuery ,
49+ useTheme ,
50+ } from '@mui/material' ;
51+ import { DarkMode , LightMode , Monitor } from '@mui/icons-material' ;
52+ import { lightGreen , red , yellow } from '@mui/material/colors' ;
4053
4154const drawerWidth : number = 240 ;
4255
@@ -88,7 +101,65 @@ const Drawer = styled(MuiDrawer, {
88101 } ,
89102} ) ) ;
90103
91- function Dashboard ( ) {
104+ function ThemeToggle ( { setThemeMode, condensed} : { setThemeMode : ( theme : PaletteMode ) => void ; condensed : boolean } ) {
105+ const [ storedTheme , setStoredTheme ] = React . useState (
106+ localStorage . getItem ( 'user-set-color-scheme' ) as 'light' | 'dark' | null ,
107+ ) ;
108+ const currentTheme = useTheme ( ) ;
109+ const systemTheme = useMediaQuery ( '(prefers-color-scheme: dark)' ) ? 'dark' : 'light' ;
110+
111+ const handleThemeOverride = ( theme : PaletteMode ) => {
112+ setThemeMode ( theme ) ;
113+ localStorage . setItem ( 'user-set-color-scheme' , theme ) ;
114+ setStoredTheme ( theme ) ;
115+ } ;
116+
117+ const handleSystemDefault = ( ) => {
118+ setThemeMode ( systemTheme ) ;
119+ localStorage . removeItem ( 'user-set-color-scheme' ) ;
120+ setStoredTheme ( null ) ;
121+ } ;
122+
123+ return (
124+ < ToggleButtonGroup size = "small" >
125+ { ( currentTheme . palette . mode != 'light' || ! condensed ) && (
126+ < Tooltip title = "Light Mode" >
127+ < ToggleButton
128+ value = "left"
129+ selected = { storedTheme === 'light' }
130+ onClick = { ( ) => handleThemeOverride ( 'light' ) }
131+ aria-label = "Light mode" >
132+ < LightMode />
133+ </ ToggleButton >
134+ </ Tooltip >
135+ ) }
136+ { ! condensed && (
137+ < Tooltip title = "System Default" >
138+ < ToggleButton
139+ value = "center"
140+ selected = { storedTheme == null }
141+ onClick = { handleSystemDefault }
142+ aria-label = "System Default" >
143+ < Monitor />
144+ </ ToggleButton >
145+ </ Tooltip >
146+ ) }
147+ { ( currentTheme . palette . mode != 'dark' || ! condensed ) && (
148+ < Tooltip title = "Dark Mode" >
149+ < ToggleButton
150+ value = "right"
151+ selected = { storedTheme === 'dark' }
152+ onClick = { ( ) => handleThemeOverride ( 'dark' ) }
153+ aria-label = "Dark mode" >
154+ < DarkMode />
155+ </ ToggleButton >
156+ </ Tooltip >
157+ ) }
158+ </ ToggleButtonGroup >
159+ ) ;
160+ }
161+
162+ function Dashboard ( { setThemeMode} : { setThemeMode : ( theme : PaletteMode ) => void } ) {
92163 const [ open , setOpen ] = React . useState ( true ) ;
93164 const toggleDrawer = ( ) => {
94165 setOpen ( ! open ) ;
@@ -137,7 +208,7 @@ function Dashboard() {
137208 textDecoration : 'none' ,
138209 } } >
139210 < Avatar src = "/logo-square.png" variant = "square" />
140- < Typography component = "h1" variant = "h5" sx = { { px : 2 } } >
211+ < Typography component = "h1" variant = "h5" sx = { { px : 2 } } color = "text.accent" >
141212 ACCESS
142213 </ Typography >
143214 </ Link >
@@ -149,12 +220,15 @@ function Dashboard() {
149220 < List component = "nav" >
150221 < NavItems open = { open } />
151222 </ List >
223+ < Stack marginTop = "auto" p = { 2 } >
224+ < ThemeToggle setThemeMode = { setThemeMode } condensed = { ! open } />
225+ </ Stack >
152226 </ Drawer >
153227 < Box
154228 component = "main"
155229 sx = { {
156230 backgroundColor : ( theme ) =>
157- theme . palette . mode === 'light' ? theme . palette . grey [ 100 ] : theme . palette . grey [ 900 ] ,
231+ theme . palette . mode === 'light' ? theme . palette . grey [ 200 ] : theme . palette . grey [ 800 ] ,
158232 flexGrow : 1 ,
159233 height : '100vh' ,
160234 overflow : 'auto' ,
@@ -189,6 +263,81 @@ function Dashboard() {
189263}
190264
191265export default function App ( ) {
266+ const storedTheme = localStorage . getItem ( 'user-set-color-scheme' ) as 'light' | 'dark' | null ;
267+ const systemTheme = useMediaQuery ( '(prefers-color-scheme: dark)' ) ? 'dark' : 'light' ;
268+ const initialMode = storedTheme ?? systemTheme ;
269+ const [ mode , setMode ] = React . useState < PaletteMode > ( initialMode ) ;
270+
271+ // See https://discord.com/branding
272+ let theme = React . useMemo ( ( ) => {
273+ const base = createTheme ( {
274+ palette : {
275+ mode,
276+ primary : {
277+ main : '#5865F2' ,
278+ light : '#A5B2FF' ,
279+ } ,
280+ secondary : {
281+ main : '#EB459E' ,
282+ } ,
283+ error : {
284+ main : '#ED4245' ,
285+ } ,
286+ warning : {
287+ main : '#FEE75C' ,
288+ } ,
289+ success : {
290+ main : '#57F287' ,
291+ } ,
292+ text : {
293+ accent : mode === 'light' ? '#5865F2' : '#A5B2FF' ,
294+ } ,
295+ } ,
296+ components : {
297+ MuiChip : {
298+ styleOverrides : {
299+ colorPrimary : ( { ownerState, theme} ) => ( {
300+ ...( ownerState . variant === 'outlined' &&
301+ ownerState . color === 'primary' && {
302+ color : theme . palette . text . accent ,
303+ borderColor : theme . palette . text . accent ,
304+ } ) ,
305+ } ) ,
306+ deleteIcon : ( { ownerState, theme} ) => ( {
307+ ...( ownerState . variant === 'outlined' &&
308+ ownerState . color === 'primary' && {
309+ color : theme . palette . text . accent ,
310+ } ) ,
311+ } ) ,
312+ } ,
313+ } ,
314+ } ,
315+ } ) ;
316+ return createTheme ( base , {
317+ palette : {
318+ highlight : {
319+ success : base . palette . augmentColor ( {
320+ color : { main : mode === 'light' ? lightGreen [ 100 ] : alpha ( lightGreen [ 500 ] , 0.3 ) } ,
321+ name : 'success' ,
322+ } ) ,
323+ warning : base . palette . augmentColor ( {
324+ color : { main : mode === 'light' ? yellow [ 100 ] : alpha ( yellow [ 500 ] , 0.3 ) } ,
325+ name : 'warning' ,
326+ } ) ,
327+ danger : base . palette . augmentColor ( {
328+ color : { main : mode === 'light' ? red [ 100 ] : alpha ( red [ 500 ] , 0.3 ) } ,
329+ name : 'danger' ,
330+ } ) ,
331+ } ,
332+ } ,
333+ } ) ;
334+ } , [ mode ] ) ;
335+
192336 useCurrentUser ( ) ;
193- return < Dashboard /> ;
337+ return (
338+ < ThemeProvider theme = { theme } >
339+ < CssBaseline />
340+ < Dashboard setThemeMode = { setMode } />
341+ </ ThemeProvider >
342+ ) ;
194343}
0 commit comments