-
-
Notifications
You must be signed in to change notification settings - Fork 373
Feat/animated theme toggle #704
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
base: main
Are you sure you want to change the base?
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,12 @@ | ||||||||||||||||||||||
| 'use client' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { Fragment, useEffect, useRef } from 'react' | ||||||||||||||||||||||
| import { Fragment, useEffect, useRef, useState } from 'react' | ||||||||||||||||||||||
| import Image from 'next/image' | ||||||||||||||||||||||
| import Link from 'next/link' | ||||||||||||||||||||||
| import { usePathname } from 'next/navigation' | ||||||||||||||||||||||
| import { Popover, Transition } from '@headlessui/react' | ||||||||||||||||||||||
| import clsx from 'clsx' | ||||||||||||||||||||||
| import { motion } from 'framer-motion' | ||||||||||||||||||||||
| import { AnimatePresence, motion, useReducedMotion } from 'framer-motion' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { Container } from '@/components/shared/Container' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -183,6 +183,29 @@ function DesktopNavigation(props) { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function ModeToggle() { | ||||||||||||||||||||||
| const [mounted, setMounted] = useState(false) | ||||||||||||||||||||||
| const [isDarkMode, setIsDarkMode] = useState(false) | ||||||||||||||||||||||
| const prefersReducedMotion = useReducedMotion() | ||||||||||||||||||||||
| const iconTransition = prefersReducedMotion | ||||||||||||||||||||||
| ? { duration: 0 } | ||||||||||||||||||||||
| : { duration: 0.5, ease: 'easeInOut' } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| setMounted(true) | ||||||||||||||||||||||
| setIsDarkMode(document.documentElement.classList.contains('dark')) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const observer = new MutationObserver(() => { | ||||||||||||||||||||||
| setIsDarkMode(document.documentElement.classList.contains('dark')) | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| observer.observe(document.documentElement, { | ||||||||||||||||||||||
| attributes: true, | ||||||||||||||||||||||
| attributeFilter: ['class'], | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return () => observer.disconnect() | ||||||||||||||||||||||
| }, []) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function disableTransitionsTemporarily() { | ||||||||||||||||||||||
| document.documentElement.classList.add('[&_*]:!transition-none') | ||||||||||||||||||||||
| window.setTimeout(() => { | ||||||||||||||||||||||
|
|
@@ -204,15 +227,42 @@ function ModeToggle() { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!mounted) { | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <div className="h-10 w-10 rounded-full bg-white/90 ring-1 ring-black/10 dark:bg-zinc-800/90 dark:ring-white/10" /> | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <button | ||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||
| aria-label="Toggle dark mode" | ||||||||||||||||||||||
| className="group rounded-full bg-white/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-black/10 hover:ring-black/20 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20" | ||||||||||||||||||||||
| className="group relative flex h-10 w-10 items-center justify-center rounded-full bg-white/90 shadow-lg shadow-zinc-800/5 ring-1 ring-black/10 hover:ring-black/20 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20" | ||||||||||||||||||||||
|
Comment on lines
237
to
+240
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. Expose the current toggle state. This is a two-state button, but it only exposes an action label right now. Adding ♿ Suggested change <button
type="button"
aria-label="Toggle dark mode"
+ aria-pressed={isDarkMode}
className="group relative flex h-10 w-10 items-center justify-center rounded-full bg-white/90 shadow-lg shadow-zinc-800/5 ring-1 ring-black/10 hover:ring-black/20 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
onClick={toggleMode}
>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| onClick={toggleMode} | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
| <SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 dark:hidden [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-yellow-400 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-yellow-500" /> | ||||||||||||||||||||||
| <MoonIcon className="hidden h-6 w-6 fill-zinc-700 stroke-zinc-500 transition dark:block [@media(prefers-color-scheme:dark)]:group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" /> | ||||||||||||||||||||||
| <AnimatePresence mode="wait" initial={false}> | ||||||||||||||||||||||
| {isDarkMode ? ( | ||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||
| key="moon" | ||||||||||||||||||||||
| initial={{ opacity: 0, rotate: -90, scale: 0.5 }} | ||||||||||||||||||||||
| animate={{ opacity: 1, rotate: 0, scale: 1 }} | ||||||||||||||||||||||
| exit={{ opacity: 0, rotate: 90, scale: 0.5 }} | ||||||||||||||||||||||
| transition={iconTransition} | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
| <MoonIcon className="h-6 w-6 fill-zinc-700 stroke-zinc-500 transition group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" /> | ||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||
| key="sun" | ||||||||||||||||||||||
| initial={{ opacity: 0, rotate: 90, scale: 0.5 }} | ||||||||||||||||||||||
| animate={{ opacity: 1, rotate: 0, scale: 1 }} | ||||||||||||||||||||||
| exit={{ opacity: 0, rotate: -90, scale: 0.5 }} | ||||||||||||||||||||||
| transition={iconTransition} | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
| <SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-yellow-400 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-yellow-500" /> | ||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||
| )} | ||||||||||||||||||||||
| </AnimatePresence> | ||||||||||||||||||||||
| </button> | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,26 +17,26 @@ export function Pattern({ | |
| let height = pattern.length * size + (pattern.length - 1) * gapY | ||
|
|
||
| return ( | ||
| <svg aria-hidden="true" width={width} height={height} {...props}> | ||
| <svg aria-hidden="true" width={width} height={height} className="overflow-visible" {...props}> | ||
| <defs> | ||
| <symbol id={`${id}-0`} width={size} height={size}> | ||
| <rect className="fill-green-200 dark:fill-yellow-200" width={size} height={size} /> | ||
| <rect className="fill-green-200 dark:fill-yellow-200 transition-colors duration-500" width={size} height={size} /> | ||
| <circle | ||
| className="fill-[#00843D] dark:fill-yellow-400" | ||
| className="fill-[#00843D] dark:fill-yellow-400 shadow-xl transition-colors duration-500" | ||
| cx={size / 2} | ||
| cy={size / 2} | ||
| r={size * (13 / 40)} | ||
| /> | ||
| </symbol> | ||
| <symbol id={`${id}-1`} width={size} height={size}> | ||
| <circle | ||
| className="fill-green-400 dark:fill-yellow-300 animate-pulse" | ||
| className="fill-green-300 dark:fill-yellow-300 animate-pulse transition-colors duration-500" | ||
| cx={size / 2} | ||
| cy={size / 2} | ||
| r={size / 2} | ||
| /> | ||
| <rect | ||
| className="fill-[#00843D] dark:fill-yellow-400" | ||
| className="fill-[#00843D] dark:fill-yellow-400 shadow-xl transition-colors duration-500" | ||
|
Comment on lines
+33
to
+39
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. 🧩 Analysis chain🏁 Script executed: fd -t f "Pattern.jsx" --maxdepth 5Repository: AOSSIE-Org/Website Length of output: 138 🏁 Script executed: git ls-files | grep -i patternRepository: AOSSIE-Org/Website Length of output: 138 🏁 Script executed: fd -t f "tailwind.config" -o "tailwind.js" | head -5Repository: AOSSIE-Org/Website Length of output: 203 🏁 Script executed: wc -l src/components/shared/Pattern.jsxRepository: AOSSIE-Org/Website Length of output: 98 🏁 Script executed: cat -n src/components/shared/Pattern.jsxRepository: AOSSIE-Org/Website Length of output: 3251 🏁 Script executed: find . -maxdepth 2 -name "tailwind.config*" -o -name "tailwind.js"Repository: AOSSIE-Org/Website Length of output: 82 🏁 Script executed: cat -n tailwind.config.jsRepository: AOSSIE-Org/Website Length of output: 13659 🏁 Script executed: grep -r "tailwindcss" package.json package-lock.json 2>/dev/null | head -10Repository: AOSSIE-Org/Website Length of output: 702 Wrap animations in The Suggested change <circle
- className="fill-green-300 dark:fill-yellow-300 animate-pulse transition-colors duration-500"
+ className="fill-green-300 dark:fill-yellow-300 motion-safe:animate-pulse motion-reduce:animate-none transition-colors duration-500"
cx={size / 2}
cy={size / 2}
r={size / 2}
/>
@@
- const hoverRotation = (rowIndex + columnIndex) % 2 === 0 ? "group-hover:rotate-[15deg]" : "group-hover:-rotate-[15deg]"
+ const hoverRotation =
+ (rowIndex + columnIndex) % 2 === 0
+ ? 'motion-safe:group-hover:rotate-[15deg]'
+ : 'motion-safe:group-hover:-rotate-[15deg]'
@@
<use
href={`#${id}-${shape}`}
x={x}
y={y}
style={{ transformOrigin: `${x + size / 2}px ${y + size / 2}px` }}
- className={`transition-all duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover:scale-[1.4] ${hoverRotation} group-hover:drop-shadow-lg pointer-events-none`}
+ className={`transition-all duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] motion-safe:group-hover:scale-[1.4] ${hoverRotation} motion-safe:group-hover:drop-shadow-lg pointer-events-none`}
/>🤖 Prompt for AI Agents |
||
| width={size / 2} | ||
| height={size / 2} | ||
| x={size / 4} | ||
|
|
@@ -45,14 +45,27 @@ export function Pattern({ | |
| </symbol> | ||
| </defs> | ||
| {pattern.map((row, rowIndex) => | ||
| row.map((shape, columnIndex) => ( | ||
| <use | ||
| key={`${rowIndex}-${columnIndex}`} | ||
| href={`#${id}-${shape}`} | ||
| x={columnIndex * size + columnIndex * gapX} | ||
| y={rowIndex * size + rowIndex * gapY} | ||
| /> | ||
| )) | ||
| row.map((shape, columnIndex) => { | ||
| const x = columnIndex * size + columnIndex * gapX; | ||
| const y = rowIndex * size + rowIndex * gapY; | ||
|
|
||
| // Determine a playful alternating rotation direction based on the grid index | ||
| const hoverRotation = (rowIndex + columnIndex) % 2 === 0 ? "group-hover:rotate-[15deg]" : "group-hover:-rotate-[15deg]"; | ||
|
|
||
| return ( | ||
| <g key={`${rowIndex}-${columnIndex}`} className="group cursor-pointer"> | ||
| {/* Invisible, static hit area to handle hover without flickering */} | ||
| <rect x={x} y={y} width={size} height={size} fill="transparent" className="pointer-events-auto" /> | ||
| <use | ||
| href={`#${id}-${shape}`} | ||
| x={x} | ||
| y={y} | ||
| style={{ transformOrigin: `${x + size / 2}px ${y + size / 2}px` }} | ||
| className={`transition-all duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover:scale-[1.4] ${hoverRotation} group-hover:drop-shadow-lg pointer-events-none`} | ||
| /> | ||
| </g> | ||
| ); | ||
| }) | ||
| )} | ||
| </svg> | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.