Skip to content

Commit 94a75e2

Browse files
committed
feature: create a button for dark/light mode
1 parent f34e073 commit 94a75e2

File tree

5 files changed

+255
-15
lines changed

5 files changed

+255
-15
lines changed
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useAppTheme } from "../../context/AppThemeContext";
22
import { useTranslation } from "react-i18next";
3+
import ToggleButton from "./ToggleButton";
34

45

56
const AppThemeToggle = () => {
@@ -12,12 +13,9 @@ const AppThemeToggle = () => {
1213
const handleClick = () => {
1314
toggleAppTheme()
1415
}
16+
17+
return <ToggleButton onChange={handleClick} checked={appTheme === 'dark'} />
1518

16-
console.log(appTheme)
17-
18-
return <div>
19-
<button onClick={handleClick}>{appTheme}</button>
20-
</div>
2119
}
2220

23-
export default AppThemeToggle;
21+
export default AppThemeToggle;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
type ToggleButtonProps = {
3+
onChange: () => void;
4+
checked: boolean;
5+
}
6+
7+
export default function ToggleButton(props : ToggleButtonProps) {
8+
return <label htmlFor="theme" className="theme" >
9+
<span className="theme__toggle-wrap">
10+
11+
<input type="checkbox" className="theme__toggle" id="theme" role="switch" {...props} />
12+
<span className="theme__fill"></span>
13+
<span className="theme__icon">
14+
<span className="theme__icon-part"></span>
15+
<span className="theme__icon-part"></span>
16+
<span className="theme__icon-part"></span>
17+
<span className="theme__icon-part"></span>
18+
<span className="theme__icon-part"></span>
19+
<span className="theme__icon-part"></span>
20+
<span className="theme__icon-part"></span>
21+
<span className="theme__icon-part"></span>
22+
<span className="theme__icon-part"></span>
23+
24+
</span>
25+
</span>
26+
</label>
27+
}

src/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import LoginProcessPage from './pages/LoginProcess'
1717
import { I18nextProvider } from 'react-i18next';
1818
import i18n from './i18n';
1919
import { Provider } from './components/ui/provider';
20+
import { AppThemeProvider } from './context/AppThemeContext';
2021

2122
// eslint-disable-next-line
2223
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" />
@@ -73,13 +74,15 @@ const routes = [
7374
const router = createBrowserRouter(routes)
7475
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7576
<React.StrictMode>
76-
<I18nextProvider i18n={i18n}>
77-
<AuthProvider>
78-
<Provider >
79-
<RouterProvider router={router} />
80-
</Provider>
81-
</AuthProvider>
82-
</I18nextProvider>
77+
<AppThemeProvider>
78+
<I18nextProvider i18n={i18n}>
79+
<AuthProvider>
80+
<Provider >
81+
<RouterProvider router={router} />
82+
</Provider>
83+
</AuthProvider>
84+
</I18nextProvider>
85+
</AppThemeProvider>
8386
</React.StrictMode>
8487
)
8588
reportWebVitals()

src/styles/index.css

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
0px 8.5px 17.9px rgba(0, 0, 0, 0.042),
1313
0px 15.9px 33.4px rgba(0, 0, 0, 0.05),
1414
0px 38px 80px rgba(0, 0, 0, 0.07);
15-
}
15+
--hue: 223;
16+
--bg: hsl(var(--hue), 10%, 100%);
17+
--fg: hsl(var(--hue), 10%, 0%);
18+
--primary: hsl(var(--hue), 90%, 55%);
19+
--primaryT: hsla(var(--hue), 90%, 55%, 0);
20+
--transDur: 0.3s;
21+
}
1622

1723
:root[app-theme="light"] {
1824
/* Light Theme Accent Palette */
@@ -97,3 +103,209 @@ code {
97103
-ms-overflow-style: none;
98104
scrollbar-width: none;
99105
}
106+
107+
/* From Uiverse.io by JkHuger */
108+
/* Default */
109+
.theme {
110+
display: flex;
111+
align-items: center;
112+
-webkit-tap-highlight-color: transparent;
113+
}
114+
115+
.theme__fill,
116+
.theme__icon {
117+
transition: 0.3s;
118+
}
119+
120+
121+
.theme__icon,
122+
.theme__toggle {
123+
z-index: 1;
124+
}
125+
126+
.theme__icon,
127+
.theme__icon-part {
128+
position: absolute;
129+
}
130+
131+
.theme__icon {
132+
133+
top: 0.7em;
134+
left: 0.7em;
135+
width: 0.7em;
136+
height: 0.7em;
137+
138+
}
139+
140+
.theme__icon-part {
141+
border-radius: 50%;
142+
box-shadow: 0.4em -0.4em 0 0.5em hsl(0,0%,100%) inset;
143+
top: calc(50% - 0.5em);
144+
left: calc(50% - 0.5em);
145+
width: 1em;
146+
height: 1em;
147+
transition: box-shadow var(--transDur) ease-in-out,
148+
opacity var(--transDur) ease-in-out,
149+
transform var(--transDur) ease-in-out;
150+
transform: scale(0.5);
151+
}
152+
153+
.theme__icon-part ~ .theme__icon-part {
154+
background-color: hsl(0,0%,100%);
155+
border-radius: 0.05em;
156+
top: 50%;
157+
left: calc(50% - 0.05em);
158+
transform: rotate(0deg) translateY(0.5em);
159+
transform-origin: 50% 0;
160+
width: 0.1em;
161+
height: 0.2em;
162+
}
163+
164+
.theme__icon-part:nth-child(3) {
165+
transform: rotate(45deg) translateY(0.45em);
166+
}
167+
168+
.theme__icon-part:nth-child(4) {
169+
transform: rotate(90deg) translateY(0.45em);
170+
}
171+
172+
.theme__icon-part:nth-child(5) {
173+
transform: rotate(135deg) translateY(0.45em);
174+
}
175+
176+
.theme__icon-part:nth-child(6) {
177+
transform: rotate(180deg) translateY(0.45em);
178+
}
179+
180+
.theme__icon-part:nth-child(7) {
181+
transform: rotate(225deg) translateY(0.45em);
182+
}
183+
184+
.theme__icon-part:nth-child(8) {
185+
transform: rotate(270deg) translateY(0.5em);
186+
}
187+
188+
.theme__icon-part:nth-child(9) {
189+
transform: rotate(315deg) translateY(0.5em);
190+
}
191+
192+
.theme__label,
193+
.theme__toggle,
194+
.theme__toggle-wrap {
195+
position: relative;
196+
}
197+
198+
.theme__toggle,
199+
.theme__toggle:before {
200+
display: block;
201+
}
202+
203+
.theme__toggle {
204+
background-color: hsl(48,90%,85%);
205+
border-radius: 25% / 50%;
206+
box-shadow: 0 0 0 0.125em var(--primaryT);
207+
padding: 0.25em;
208+
width: 4.3em;
209+
height: 2.3em;
210+
-webkit-appearance: none;
211+
appearance: none;
212+
transition: background-color var(--transDur) ease-in-out,
213+
box-shadow 0.15s ease-in-out,
214+
transform var(--transDur) ease-in-out;
215+
}
216+
217+
.theme__toggle:before {
218+
background-color: hsl(48,90%,55%);
219+
border-radius: 50%;
220+
content: "";
221+
width: 1.7em;
222+
height: 1.7em;
223+
transition: 0.3s;
224+
}
225+
226+
.theme__toggle:focus {
227+
box-shadow: 0 0 0 0.125em var(--primary);
228+
outline: transparent;
229+
}
230+
231+
/* Checked */
232+
.theme__toggle:checked {
233+
background-color: hsl(198,90%,15%);
234+
}
235+
236+
.theme__toggle:checked:before,
237+
.theme__toggle:checked ~ .theme__icon {
238+
transform: translateX(2rem);
239+
240+
241+
}
242+
.theme__toggle:checked ~ .theme__icon {
243+
244+
top:0.4em;
245+
246+
}
247+
248+
.theme__toggle:checked:before {
249+
background-color: hsl(198,90%,55%);
250+
}
251+
252+
.theme__toggle:checked ~ .theme__fill {
253+
transform: translateX(0);
254+
}
255+
256+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(1) {
257+
box-shadow: 0.2em -0.2em 0 0.2em hsl(0,0%,100%) inset;
258+
transform: scale(1);
259+
top: 0.2em;
260+
left: -0.2em;
261+
}
262+
263+
.theme__toggle:checked ~ .theme__icon .theme__icon-part ~ .theme__icon-part {
264+
opacity: 0;
265+
}
266+
267+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(2) {
268+
transform: rotate(45deg) translateY(0.8em);
269+
}
270+
271+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(3) {
272+
transform: rotate(90deg) translateY(0.8em);
273+
}
274+
275+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(4) {
276+
transform: rotate(135deg) translateY(0.8em);
277+
}
278+
279+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(5) {
280+
transform: rotate(180deg) translateY(0.8em);
281+
}
282+
283+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(6) {
284+
transform: rotate(225deg) translateY(0.8em);
285+
}
286+
287+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(7) {
288+
transform: rotate(270deg) translateY(0.8em);
289+
}
290+
291+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(8) {
292+
transform: rotate(315deg) translateY(0.8em);
293+
}
294+
295+
.theme__toggle:checked ~ .theme__icon .theme__icon-part:nth-child(9) {
296+
transform: rotate(360deg) translateY(0.8em);
297+
}
298+
299+
.theme__toggle-wrap {
300+
margin: 0 0.75em;
301+
}
302+
303+
@supports selector(:focus-visible) {
304+
.theme__toggle:focus {
305+
box-shadow: 0 0 0 0.125em var(--primaryT);
306+
}
307+
308+
.theme__toggle:focus-visible {
309+
box-shadow: 0 0 0 0.125em var(--primary);
310+
}
311+
}

tailwind.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type {import('tailwindcss').Config} */
22
export default {
33
content: ["./src/**/*.{js,jsx,ts,tsx}"],
4-
darkMode: "dark",
4+
darkMode: "class",
55
theme: {
66
container: {
77
center: true,

0 commit comments

Comments
 (0)