Skip to content

Commit fa93c9a

Browse files
committed
feat: add dark mode support
1 parent 0878d75 commit fa93c9a

File tree

14 files changed

+255
-1921
lines changed

14 files changed

+255
-1921
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"dependencies": {
2525
"@coreui/chartjs": "^3.1.1",
26-
"@coreui/coreui": "^4.2.6",
26+
"@coreui/coreui": "4.3.0-alpha.0",
2727
"@coreui/icons": "^3.0.1",
2828
"@coreui/icons-react": "^2.1.0",
2929
"@coreui/react": "^4.6.0",

src/App.js

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
import React, { Component, Suspense } from 'react'
1+
import React, { Suspense, useEffect } from 'react'
22
import { HashRouter, Route, Routes } from 'react-router-dom'
3+
import { useSelector } from 'react-redux'
4+
5+
import { CSpinner } from '@coreui/react'
36
import './scss/style.scss'
47

58
const loading = (
69
<div className="pt-3 text-center">
7-
<div className="sk-spinner sk-spinner-pulse"></div>
10+
<CSpinner color="primary" variant="grow" />
811
</div>
912
)
1013

14+
const getPreferredTheme = (storedTheme) => {
15+
if (storedTheme) {
16+
return storedTheme
17+
}
18+
19+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
20+
}
21+
22+
const setTheme = (theme) => {
23+
document.documentElement.dataset.coreuiTheme =
24+
theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : theme
25+
}
26+
1127
// Containers
1228
const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout'))
1329

@@ -17,22 +33,35 @@ const Register = React.lazy(() => import('./views/pages/register/Register'))
1733
const Page404 = React.lazy(() => import('./views/pages/page404/Page404'))
1834
const Page500 = React.lazy(() => import('./views/pages/page500/Page500'))
1935

20-
class App extends Component {
21-
render() {
22-
return (
23-
<HashRouter>
24-
<Suspense fallback={loading}>
25-
<Routes>
26-
<Route exact path="/login" name="Login Page" element={<Login />} />
27-
<Route exact path="/register" name="Register Page" element={<Register />} />
28-
<Route exact path="/404" name="Page 404" element={<Page404 />} />
29-
<Route exact path="/500" name="Page 500" element={<Page500 />} />
30-
<Route path="*" name="Home" element={<DefaultLayout />} />
31-
</Routes>
32-
</Suspense>
33-
</HashRouter>
34-
)
36+
const App = () => {
37+
const theme = useSelector((state) => state.theme)
38+
39+
if (theme) {
40+
document.documentElement.dataset.coreuiTheme =
41+
theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : theme
3542
}
43+
44+
useEffect(() => {
45+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
46+
if (theme !== 'light' || theme !== 'dark') {
47+
setTheme(getPreferredTheme(theme))
48+
}
49+
})
50+
}, [])
51+
52+
return (
53+
<HashRouter>
54+
<Suspense fallback={loading}>
55+
<Routes>
56+
<Route exact path="/login" name="Login Page" element={<Login />} />
57+
<Route exact path="/register" name="Register Page" element={<Register />} />
58+
<Route exact path="/404" name="Page 404" element={<Page404 />} />
59+
<Route exact path="/500" name="Page 500" element={<Page500 />} />
60+
<Route path="*" name="Home" element={<DefaultLayout />} />
61+
</Routes>
62+
</Suspense>
63+
</HashRouter>
64+
)
3665
}
3766

3867
export default App

src/components/AppHeader.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { NavLink } from 'react-router-dom'
33
import { useSelector, useDispatch } from 'react-redux'
44
import {
55
CContainer,
6+
CDropdown,
7+
CDropdownItem,
8+
CDropdownMenu,
9+
CDropdownToggle,
610
CHeader,
711
CHeaderBrand,
812
CHeaderDivider,
@@ -12,7 +16,15 @@ import {
1216
CNavItem,
1317
} from '@coreui/react'
1418
import CIcon from '@coreui/icons-react'
15-
import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons'
19+
import {
20+
cilBell,
21+
cilContrast,
22+
cilEnvelopeOpen,
23+
cilList,
24+
cilMenu,
25+
cilMoon,
26+
cilSun,
27+
} from '@coreui/icons'
1628

1729
import { AppBreadcrumb } from './index'
1830
import { AppHeaderDropdown } from './header/index'
@@ -21,6 +33,7 @@ import { logo } from 'src/assets/brand/logo'
2133
const AppHeader = () => {
2234
const dispatch = useDispatch()
2335
const sidebarShow = useSelector((state) => state.sidebarShow)
36+
const theme = useSelector((state) => state.theme)
2437

2538
return (
2639
<CHeader position="sticky" className="mb-4">
@@ -63,6 +76,52 @@ const AppHeader = () => {
6376
<CIcon icon={cilEnvelopeOpen} size="lg" />
6477
</CNavLink>
6578
</CNavItem>
79+
<li className="nav-item py-2 py-lg-1">
80+
<div className="vr h-100 mx-2 text-body text-opacity-75"></div>
81+
</li>
82+
<CDropdown variant="nav-item" placement="bottom-end">
83+
<CDropdownToggle caret={false}>
84+
{theme === 'dark' ? (
85+
<CIcon icon={cilMoon} size="xl" />
86+
) : theme === 'auto' ? (
87+
<CIcon icon={cilContrast} size="xl" />
88+
) : (
89+
<CIcon icon={cilSun} size="xl" />
90+
)}
91+
</CDropdownToggle>
92+
<CDropdownMenu>
93+
<CDropdownItem
94+
active={theme === 'light'}
95+
className="d-flex align-items-center"
96+
component="button"
97+
type="button"
98+
onClick={() => dispatch({ type: 'setTheme', theme: 'light' })}
99+
>
100+
<CIcon className="me-2" icon={cilSun} size="lg" /> Light
101+
</CDropdownItem>
102+
<CDropdownItem
103+
active={theme === 'dark'}
104+
className="d-flex align-items-center"
105+
component="button"
106+
type="button"
107+
onClick={() => dispatch({ type: 'setTheme', theme: 'dark' })}
108+
>
109+
<CIcon className="me-2" icon={cilMoon} size="lg" /> Dark
110+
</CDropdownItem>
111+
<CDropdownItem
112+
active={theme === 'auto'}
113+
className="d-flex align-items-center"
114+
component="button"
115+
type="button"
116+
onClick={() => dispatch({ type: 'setTheme', theme: 'auto' })}
117+
>
118+
<CIcon className="me-2" icon={cilContrast} size="lg" /> Auto
119+
</CDropdownItem>
120+
</CDropdownMenu>
121+
</CDropdown>
122+
<li className="nav-item py-2 py-lg-1">
123+
<div className="vr h-100 mx-2 text-body text-opacity-75"></div>
124+
</li>
66125
</CHeaderNav>
67126
<CHeaderNav className="ms-3">
68127
<AppHeaderDropdown />

src/components/header/AppHeaderDropdown.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const AppHeaderDropdown = () => {
3131
<CAvatar src={avatar8} size="md" />
3232
</CDropdownToggle>
3333
<CDropdownMenu className="pt-0" placement="bottom-end">
34-
<CDropdownHeader className="bg-light fw-semibold py-2">Account</CDropdownHeader>
34+
<CDropdownHeader className="bg-body-secondary fw-semibold py-2">Account</CDropdownHeader>
3535
<CDropdownItem href="#">
3636
<CIcon icon={cilBell} className="me-2" />
3737
Updates
@@ -60,7 +60,7 @@ const AppHeaderDropdown = () => {
6060
42
6161
</CBadge>
6262
</CDropdownItem>
63-
<CDropdownHeader className="bg-light fw-semibold py-2">Settings</CDropdownHeader>
63+
<CDropdownHeader className="bg-body-secondary fw-semibold py-2">Settings</CDropdownHeader>
6464
<CDropdownItem href="#">
6565
<CIcon icon={cilUser} className="me-2" />
6666
Profile

src/layout/DefaultLayout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const DefaultLayout = () => {
55
return (
66
<div>
77
<AppSidebar />
8-
<div className="wrapper d-flex flex-column min-vh-100 bg-light">
8+
<div className="wrapper d-flex flex-column min-vh-100 bg-body-tertiary">
99
<AppHeader />
1010
<div className="body flex-grow-1 px-3">
1111
<AppContent />

src/scss/_example.scss

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
1+
/* stylelint-disable declaration-no-important, scss/selector-no-redundant-nesting-selector */
2+
@import "@coreui/coreui/scss/functions";
3+
@import "@coreui/coreui/scss/variables";
4+
@import "@coreui/coreui/scss/mixins";
5+
16
.example {
27
&:not(:first-child) {
38
margin-top: 1.5rem;
49
}
510

611
.tab-content {
7-
background-color: $light-50 !important;
8-
9-
@at-root .dark-theme & {
10-
background-color: rgba(255, 255, 255, .1) !important;
11-
}
12-
}
13-
14-
code[class*="language-"],
15-
pre[class*="language-"] {
16-
font-size: .875rem !important;
17-
}
18-
19-
:not(pre) > code[class*="language-"],
20-
pre[class*="language-"] {
21-
background: transparent;
12+
background-color: var(--#{$prefix}tertiary-bg) !important;
2213
}
2314

2415
& + p {
25-
margin-top: 1.5rem
16+
margin-top: 1.5rem;
2617
}
2718

2819
// Components examples
29-
.preview,
30-
.preview .col {
20+
.preview {
3121
+ p {
3222
margin-top: 2rem;
3323
}
@@ -105,5 +95,20 @@
10595
margin-top: .5rem;
10696
margin-bottom: .5rem;
10797
}
98+
99+
.docs-example-modal {
100+
.modal {
101+
position: static;
102+
display: block;
103+
}
104+
}
108105
}
109106
}
107+
108+
@if $enable-dark-mode {
109+
@include color-mode(dark) {
110+
.example .tab-content {
111+
background-color: var(--#{$prefix}secondary-bg) !important;
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)