-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add system theme detection for automatic dark/light mode #14
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
Changes from all commits
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,7 @@ import React from "react"; | |||||
| import type { supportUsButtonProps } from "../types/index"; | ||||||
| import type { Theme } from "../types/index"; | ||||||
| import type { ButtonVariant } from "../types/index"; | ||||||
| import type { Tier } from "../types/index"; | ||||||
|
|
||||||
| // Function to get the appropriate classes based on the selected theme, used for styling different sections of the component according to the chosen theme | ||||||
| function classAccordingToTheme(Theme: Theme): string { | ||||||
|
|
@@ -70,10 +71,24 @@ function SupportUsButton({ | |||||
| }, | ||||||
| buttonVariant = "AOSSIE", | ||||||
| }: supportUsButtonProps): React.JSX.Element { | ||||||
| const sortedSponsors = sponsors ? [...sponsors].sort((a, b) => { | ||||||
| const tierPriority: Record<Tier, number> = { | ||||||
| Platinum: 1, | ||||||
| Gold: 2, | ||||||
| Silver: 3, | ||||||
| Bronze: 4, | ||||||
| }; | ||||||
| const aTier = a.sponsorshipTier || 'Bronze'; | ||||||
| const bTier = b.sponsorshipTier || 'Bronze'; | ||||||
| return tierPriority[aTier] - tierPriority[bTier]; | ||||||
| }) : []; | ||||||
|
|
||||||
| const resolvedTheme = Theme === "system" ? (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : Theme; | ||||||
|
|
||||||
| return ( | ||||||
| // Container for the support us button, with dynamic classes based on the selected theme and custom class names | ||||||
| <div | ||||||
| className={`w-full font-sans justify-center items-center text-center ${Theme == "light" || Theme == "dark" ? classAccordingToTheme(Theme) : "bg-black text-white"} ${classNames.container}`} | ||||||
| className={`w-full font-sans justify-center items-center text-center ${resolvedTheme == "light" || resolvedTheme == "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`} | ||||||
|
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. 🧹 Nitpick | 🔵 Trivial Use strict equality ( Line 91 uses loose equality ( ♻️ Proposed fix- className={`w-full font-sans justify-center items-center text-center ${resolvedTheme == "light" || resolvedTheme == "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}
+ className={`w-full font-sans justify-center items-center text-center ${resolvedTheme === "light" || resolvedTheme === "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| > | ||||||
| {/* Hero section with optional background image*/} | ||||||
| <div className="relative w-full h-[50vh] flex justify-center"> | ||||||
|
|
@@ -119,7 +134,7 @@ function SupportUsButton({ | |||||
| {hero.title} | ||||||
| </h1> | ||||||
| <p | ||||||
| className={`wrap-anywhere ${Theme === "light" ? "text-slate-600" : "text-slate-400"} text-lg font-semibold`} | ||||||
| className={`wrap-anywhere ${resolvedTheme === "light" ? "text-slate-600" : "text-slate-400"} text-lg font-semibold`} | ||||||
| > | ||||||
| {hero.description} | ||||||
| </p> | ||||||
|
|
@@ -134,21 +149,21 @@ function SupportUsButton({ | |||||
| relative w-[90%] p-15 rounded-2xl overflow-visible | ||||||
|
|
||||||
| // Shadows for different themes | ||||||
| ${Theme === "AOSSIE" && "shadow-xl shadow-primary/20"} | ||||||
| ${Theme === "light" && "shadow-xl shadow-gray-300/30"} | ||||||
| ${Theme === "dark" && "shadow-xl shadow-gray-700/30"} | ||||||
| ${Theme === "minimal" && "shadow-xl shadow-gray-800/30"} | ||||||
| ${Theme === "corporate" && "shadow-xl shadow-blue-600/30"} | ||||||
| ${resolvedTheme === "AOSSIE" && "shadow-xl shadow-primary/20"} | ||||||
| ${resolvedTheme === "light" && "shadow-xl shadow-gray-300/30"} | ||||||
| ${resolvedTheme === "dark" && "shadow-xl shadow-gray-700/30"} | ||||||
| ${resolvedTheme === "minimal" && "shadow-xl shadow-gray-800/30"} | ||||||
| ${resolvedTheme === "corporate" && "shadow-xl shadow-blue-600/30"} | ||||||
|
|
||||||
| // Outline for light and dark themes | ||||||
| ${Theme === "light" && "outline-1 outline-gray-300"} | ||||||
| ${Theme === "dark" && "outline-1 outline-gray-700"} | ||||||
| ${classAccordingToTheme(Theme)}`} | ||||||
| ${resolvedTheme === "light" && "outline-1 outline-gray-300"} | ||||||
| ${resolvedTheme === "dark" && "outline-1 outline-gray-700"} | ||||||
| ${classAccordingToTheme(resolvedTheme)}`} | ||||||
|
Comment on lines
+152
to
+161
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. 🧹 Nitpick | 🔵 Trivial Consider extracting repeated theme-based class patterns into a helper. The pattern ♻️ Example helper approachconst themeStyles = {
shadows: {
AOSSIE: "shadow-xl shadow-primary/20",
light: "shadow-xl shadow-gray-300/30",
dark: "shadow-xl shadow-gray-700/30",
minimal: "shadow-xl shadow-gray-800/30",
corporate: "shadow-xl shadow-blue-600/30",
},
// ... other categories
} as const;
// Usage:
className={`... ${themeStyles.shadows[resolvedTheme]} ...`}🤖 Prompt for AI Agents |
||||||
| > | ||||||
| {/* Background grid */} | ||||||
| <div className="absolute inset-0 bg-[radial-gradient(rgba(0,0,0,0.15)_1.5px,transparent_0)] bg-size-[20px_20px] pointer-events-none opacity-100"></div> | ||||||
| <div | ||||||
| className={`absolute top-0 left-0 bottom-0 w-1/2 h-full rounded-2xl p-6 overflow-visible ${classAccordingToTheme(Theme)}`} | ||||||
| className={`absolute top-0 left-0 bottom-0 w-1/2 h-full rounded-2xl p-6 overflow-visible ${classAccordingToTheme(resolvedTheme)}`} | ||||||
| ></div> | ||||||
|
|
||||||
| {/* Content container */} | ||||||
|
|
@@ -196,32 +211,32 @@ function SupportUsButton({ | |||||
| <div | ||||||
| className={` | ||||||
| border | ||||||
| ${Theme === "AOSSIE" && "border-[#f1c514]/50"} | ||||||
| ${Theme === "light" && "border-gray-300/50"} | ||||||
| ${Theme === "dark" && "border-gray-700/50"} | ||||||
| ${Theme === "minimal" && "border-gray-800/50"} | ||||||
| ${Theme === "corporate" && "border-blue-600/50"}`} | ||||||
| ${resolvedTheme === "AOSSIE" && "border-[#f1c514]/50"} | ||||||
| ${resolvedTheme === "light" && "border-gray-300/50"} | ||||||
| ${resolvedTheme === "dark" && "border-gray-700/50"} | ||||||
| ${resolvedTheme === "minimal" && "border-gray-800/50"} | ||||||
| ${resolvedTheme === "corporate" && "border-blue-600/50"}`} | ||||||
| ></div> | ||||||
|
|
||||||
| {/* Project information */} | ||||||
| <div className="flex flex-col gap-2"> | ||||||
| <h3 | ||||||
| className={`font-bold w-fit uppercase text-sm p-2 rounded-lg | ||||||
| ${Theme === "AOSSIE" && "bg-[#edc214]"} | ||||||
| ${Theme === "light" && "bg-gray-300/50"} | ||||||
| ${Theme === "dark" && "bg-gray-700/50"} | ||||||
| ${Theme === "minimal" && "bg-gray-800/50"} | ||||||
| ${Theme === "corporate" && "bg-blue-600/50"}`} | ||||||
| ${resolvedTheme === "AOSSIE" && "bg-[#edc214]"} | ||||||
| ${resolvedTheme === "light" && "bg-gray-300/50"} | ||||||
| ${resolvedTheme === "dark" && "bg-gray-700/50"} | ||||||
| ${resolvedTheme === "minimal" && "bg-gray-800/50"} | ||||||
| ${resolvedTheme === "corporate" && "bg-blue-600/50"}`} | ||||||
| > | ||||||
| ABOUT PROJECT: {organizationInformation.projectInformation.name} | ||||||
| </h3> | ||||||
| <p | ||||||
| className={`italic font-semibold | ||||||
| ${Theme === "AOSSIE" && "text-[#614f08]"} | ||||||
| ${Theme === "light" && "text-gray-600"} | ||||||
| ${Theme === "dark" && "text-gray-400"} | ||||||
| ${Theme === "minimal" && "text-gray-800"} | ||||||
| ${Theme === "corporate" && "text-blue-600/80"} | ||||||
| ${resolvedTheme === "AOSSIE" && "text-[#614f08]"} | ||||||
| ${resolvedTheme === "light" && "text-gray-600"} | ||||||
| ${resolvedTheme === "dark" && "text-gray-400"} | ||||||
| ${resolvedTheme === "minimal" && "text-gray-800"} | ||||||
| ${resolvedTheme === "corporate" && "text-blue-600/80"} | ||||||
| `} | ||||||
| > | ||||||
| "{organizationInformation.projectInformation.description}" | ||||||
|
|
@@ -234,16 +249,16 @@ function SupportUsButton({ | |||||
| {/* Sponsors section */} | ||||||
| <div | ||||||
| className={`w-full flex justify-center mt-10 p-10 | ||||||
| ${Theme === "AOSSIE" && "bg-[#1f1f1f]"} | ||||||
| ${Theme === "light" && "bg-gray-300/50"} | ||||||
| ${Theme === "dark" && "bg-gray-700/50"} | ||||||
| ${Theme === "minimal" && "bg-gray-800/50"} | ||||||
| ${Theme === "corporate" && "bg-blue-600/50"}`} | ||||||
| ${resolvedTheme === "AOSSIE" && "bg-[#1f1f1f]"} | ||||||
| ${resolvedTheme === "light" && "bg-gray-300/50"} | ||||||
| ${resolvedTheme === "dark" && "bg-gray-700/50"} | ||||||
| ${resolvedTheme === "minimal" && "bg-gray-800/50"} | ||||||
| ${resolvedTheme === "corporate" && "bg-blue-600/50"}`} | ||||||
| > | ||||||
| {sponsors && sponsors.length > 0 && ( | ||||||
| {sortedSponsors && sortedSponsors.length > 0 && ( | ||||||
| // List of sponsors with their logos and links, styled according to the selected theme and custom class names | ||||||
| <div | ||||||
| className={`${classNames.sponsors} ${classAccordingToTheme(Theme)} | ||||||
| className={`${classNames.sponsors} ${classAccordingToTheme(resolvedTheme)} | ||||||
| relative w-[90%] flex flex-col p-8 rounded-2xl gap-25 mt-15 overflow-hidden`} | ||||||
| > | ||||||
| {/* Sponsor pattern AOSSIE */} | ||||||
|
|
@@ -275,7 +290,7 @@ function SupportUsButton({ | |||||
|
|
||||||
| {/* Sponsor logos */} | ||||||
| <div className="flex flex-row flex-wrap justify-center items-center gap-10 z-10"> | ||||||
| {sponsors.map((sponsor, index) => ( | ||||||
| {sortedSponsors.map((sponsor, index) => ( | ||||||
| <a | ||||||
| href={sponsor.link} | ||||||
| key={index} | ||||||
|
|
@@ -284,14 +299,14 @@ function SupportUsButton({ | |||||
| title={`Visit ${sponsor.name}'s website`} | ||||||
| > | ||||||
| <div | ||||||
| className={`${Theme === "dark" ? "bg-gray-800 text-white" : "bg-white text-black"} rounded-lg flex flex-col justify-center items-center gap-2 p-8 w-fit transition-transform hover:scale-[1.02] shadow-lg min-h-75 min-w-62.5 hover:border-2 | ||||||
| className={`${resolvedTheme === "dark" ? "bg-gray-800 text-white" : "bg-white text-black"} rounded-lg flex flex-col justify-center items-center gap-2 p-8 w-fit transition-transform hover:scale-[1.02] shadow-lg min-h-75 min-w-62.5 hover:border-2 | ||||||
|
|
||||||
| // Shadows for different themes | ||||||
| ${Theme === "AOSSIE" && "shadow-primary/20"} | ||||||
| ${Theme === "light" && "shadow-gray-300/30"} | ||||||
| ${Theme === "dark" && "shadow-gray-700/30"} | ||||||
| ${Theme === "minimal" && "shadow-gray-800/30"} | ||||||
| ${Theme === "corporate" && "shadow-blue-600/30"} | ||||||
| ${resolvedTheme === "AOSSIE" && "shadow-primary/20"} | ||||||
| ${resolvedTheme === "light" && "shadow-gray-300/30"} | ||||||
| ${resolvedTheme === "dark" && "shadow-gray-700/30"} | ||||||
| ${resolvedTheme === "minimal" && "shadow-gray-800/30"} | ||||||
| ${resolvedTheme === "corporate" && "shadow-blue-600/30"} | ||||||
|
|
||||||
| // Size based on sponsorship tier | ||||||
| ${sponsor.sponsorshipTier === "Platinum" && "min-w-80 min-h-90"} | ||||||
|
|
@@ -405,15 +420,15 @@ function SupportUsButton({ | |||||
|
|
||||||
| {/* Call-to-action section with title, description, and sponsor links */} | ||||||
| <div | ||||||
| className={`w-full flex justify-center p-10 ${(Theme === "light" || Theme === "dark") && classAccordingToTheme(Theme)} ${classNames.ctaSection}`} | ||||||
| className={`w-full flex justify-center p-10 ${(resolvedTheme === "light" || resolvedTheme === "dark") && classAccordingToTheme(resolvedTheme)} ${classNames.ctaSection}`} | ||||||
| > | ||||||
| <div className="w-4/5 flex flex-col items-center gap-5 py-20 border border-primary rounded-sm mt-20 mb-20"> | ||||||
| <h2 className={`font-extrabold text-4xl md:text-5xl lg:text-6xl`}> | ||||||
| {ctaSection.title} | ||||||
| </h2> | ||||||
| <p | ||||||
| className={`font-semibold | ||||||
| ${Theme === "light" ? "text-gray-600" : "text-gray-400"}`} | ||||||
| ${resolvedTheme === "light" ? "text-gray-600" : "text-gray-400"}`} | ||||||
| > | ||||||
| {ctaSection.description} | ||||||
| </p> | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SSR hydration mismatch and missing reactive updates for system theme.
Accessing
window.matchMediaduring render causes two issues:"light"(window undefined), but client may resolve to"dark", causing React hydration errors.🐛 Proposed fix using useState and useEffect
Then replace the inline resolution with a hook pattern:
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the review suggestions. I have updated the implementation to address the SSR hydration issue and applied the recommended improvements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Let me verify the latest state of the file to confirm the fix.
🏁 Script executed:
Length of output: 228
🏁 Script executed:
Length of output: 92
🏁 Script executed:
Length of output: 685
🏁 Script executed:
Length of output: 3704
@madhavansingh, thanks for the response! However, after checking the latest commit (d01aa31) on the PR branch, the original inline implementation is still present at Line 86 —useState/useEffectare not yet imported or used:It looks like the fix may not have been pushed yet. Could you double-check and push the updated commit? The proposed fix from the review should look like this:
Once the updated commit is pushed, I'll be happy to re-verify! 🐇
🧠 Learnings used