-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Add dark mode switch #1409
Changes from all commits
55225ad
6d6a13a
80dee67
dd488fe
efa106f
a97b2f0
3ab8b12
cdcefb1
c565cc7
e6faf96
0e8b82b
663f233
acefedd
7c5c350
76dc4eb
b97e70f
4e85e60
9ef1c35
3f7d3ef
b5a125b
0c16a62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { useTheme } from "@mui/material"; | ||
import "./index.css"; | ||
|
||
const SunAndMoonIcon = () => { | ||
const theme = useTheme(); | ||
|
||
return ( | ||
<svg | ||
className="sun-and-moon" | ||
aria-hidden="true" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
> | ||
<mask | ||
className="moon" | ||
id="moon-mask" | ||
> | ||
<rect | ||
x="0" | ||
y="0" | ||
width="100%" | ||
height="100%" | ||
fill="#fff" | ||
/> | ||
<circle | ||
cx="24" | ||
cy="10" | ||
r="6" | ||
fill="#000" | ||
/> | ||
</mask> | ||
<circle | ||
className="sun" | ||
cx="12" | ||
cy="12" | ||
r="6" | ||
fill={theme.palette.text.secondary} | ||
mask="url(#moon-mask)" | ||
/> | ||
<g | ||
className="sun-beams" | ||
stroke={theme.palette.text.secondary} | ||
> | ||
<line | ||
x1="12" | ||
y1="1" | ||
x2="12" | ||
y2="3" | ||
/> | ||
<line | ||
x1="12" | ||
y1="21" | ||
x2="12" | ||
y2="23" | ||
/> | ||
<line | ||
x1="4.22" | ||
y1="4.22" | ||
x2="5.64" | ||
y2="5.64" | ||
/> | ||
<line | ||
x1="18.36" | ||
y1="18.36" | ||
x2="19.78" | ||
y2="19.78" | ||
/> | ||
<line | ||
x1="1" | ||
y1="12" | ||
x2="3" | ||
y2="12" | ||
/> | ||
<line | ||
x1="21" | ||
y1="12" | ||
x2="23" | ||
y2="12" | ||
/> | ||
<line | ||
x1="4.22" | ||
y1="19.78" | ||
x2="5.64" | ||
y2="18.36" | ||
/> | ||
<line | ||
x1="18.36" | ||
y1="5.64" | ||
x2="19.78" | ||
y2="4.22" | ||
/> | ||
</g> | ||
</svg> | ||
); | ||
}; | ||
|
||
export default SunAndMoonIcon; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
.sun-and-moon > :is(.moon, .sun, .sun-beams) { | ||
transform-origin: center; | ||
} | ||
|
||
.theme-toggle .sun-and-moon > .sun-beams { | ||
stroke-width: 2px; | ||
} | ||
|
||
.theme-dark .sun-and-moon > .sun { | ||
transform: scale(1.75); | ||
} | ||
|
||
.theme-dark .sun-and-moon > .sun-beams { | ||
opacity: 0; | ||
} | ||
|
||
.theme-dark .sun-and-moon > .moon > circle { | ||
transform: translateX(-7px); | ||
} | ||
|
||
@supports (cx: 1) { | ||
.theme-dark .sun-and-moon > .moon > circle { | ||
cx: 17; | ||
transform: translateX(0); | ||
} | ||
} | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
.sun-and-moon > .sun { | ||
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55); | ||
} | ||
|
||
.sun-and-moon > .sun-beams { | ||
transition: | ||
transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), | ||
opacity 0.5s cubic-bezier(0.25, 0.1, 0.25, 1); | ||
} | ||
|
||
.sun-and-moon .moon > circle { | ||
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); | ||
} | ||
|
||
@supports (cx: 1) { | ||
.sun-and-moon .moon > circle { | ||
transition: cx 0.25s cubic-bezier(0.4, 0, 0.2, 1); | ||
} | ||
} | ||
|
||
.theme-dark .sun-and-moon > .sun { | ||
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); | ||
transition-duration: 0.25s; | ||
transform: scale(1.75); | ||
} | ||
|
||
.theme-dark .sun-and-moon > .sun-beams { | ||
transition-duration: 0.15s; | ||
transform: rotateZ(-25deg); | ||
} | ||
|
||
.theme-dark .sun-and-moon > .moon > circle { | ||
transition-duration: 0.5s; | ||
transition-delay: 0.25s; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** | ||
* ThemeSwitch Component | ||
* Dark and Light Theme Switch | ||
* Original Code: https://web.dev/patterns/theming/theme-switch | ||
* License: Apache License 2.0 | ||
* Copyright © Google LLC | ||
* | ||
* This code has been adapted for use in this project. | ||
* Apache License: https://www.apache.org/licenses/LICENSE-2.0 | ||
*/ | ||
|
||
import { IconButton } from "@mui/material"; | ||
import SunAndMoonIcon from "./SunAndMoonIcon"; | ||
import { useDispatch, useSelector } from "react-redux"; | ||
import { setMode } from "../../Features/UI/uiSlice"; | ||
import "./index.css"; | ||
|
||
const ThemeSwitch = ({ width = 48, height = 48 }) => { | ||
const mode = useSelector((state) => state.ui.mode); | ||
const dispatch = useDispatch(); | ||
|
||
const toggleTheme = () => { | ||
dispatch(setMode(mode === "light" ? "dark" : "light")); | ||
}; | ||
|
||
return ( | ||
<IconButton | ||
id="theme-toggle" | ||
title="Toggles light & dark" | ||
className={`theme-${mode}`} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really liked this approach =) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome! Thank you. I really appreciate that. |
||
aria-label="auto" | ||
aria-live="polite" | ||
onClick={toggleTheme} | ||
sx={{ | ||
width, | ||
height, | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
}} | ||
> | ||
<SunAndMoonIcon /> | ||
</IconButton> | ||
); | ||
}; | ||
|
||
export default ThemeSwitch; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,43 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Box, Typography, useTheme } from "@mui/material"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import PropTypes from "prop-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useNavigate } from "react-router"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ForgotPasswordLabel = ({ email, errorEmail }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const theme = useTheme(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const handleNavigate = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (email !== "" && !errorEmail) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sessionStorage.setItem("email", email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
navigate("/forgot-password"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Box textAlign="center"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Typography | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="forgot-p" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
display="inline-block" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color={theme.palette.primary.main} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Forgot password? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Typography | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
component="span" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color={theme.palette.primary.main} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ml={theme.spacing(2)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sx={{ userSelect: "none" }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClick={handleNavigate} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Reset password | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+25
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Yo, let's make this reset password link more accessible! The reset password text should be more interactive and accessible:
<Typography
- component="span"
+ component="button"
color={theme.palette.primary.main}
ml={theme.spacing(2)}
- sx={{ userSelect: "none" }}
+ sx={{
+ userSelect: "none",
+ cursor: "pointer",
+ background: "none",
+ border: "none",
+ padding: 0,
+ '&:hover': {
+ textDecoration: 'underline'
+ }
+ }}
onClick={handleNavigate}
>
Reset password
</Typography> 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ForgotPasswordLabel.proptype = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
email: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emailError: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+38
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yo, we got some PropTypes issues that's making me nervous! There are two critical issues in the PropTypes declaration:
-ForgotPasswordLabel.proptype = {
+ForgotPasswordLabel.propTypes = {
email: PropTypes.string.isRequired,
- emailError: PropTypes.string.isRequired,
+ errorEmail: PropTypes.string.isRequired,
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default ForgotPasswordLabel; |
This comment was marked as off-topic.
Sorry, something went wrong.