fixed animation for mobile nav, and theme toggle
All checks were successful
Deploy Jody's App / build-and-deploy (push) Successful in 15s
All checks were successful
Deploy Jody's App / build-and-deploy (push) Successful in 15s
This commit is contained in:
@@ -71,10 +71,10 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`md:hidden overflow-hidden transition-[max-height,opacity,transform] duration-300 ease-out ${
|
className={`md:hidden transition-[max-height,opacity,transform] duration-300 ease-out ${
|
||||||
open
|
open
|
||||||
? "max-h-96 opacity-100 translate-y-0"
|
? "max-h-96 overflow-visible opacity-100 translate-y-0"
|
||||||
: "max-h-0 opacity-0 -translate-y-2 pointer-events-none"
|
: "max-h-0 overflow-hidden opacity-0 -translate-y-2 pointer-events-none"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="space-y-2 border-t border-secondary bg-bg px-4 py-3">
|
<div className="space-y-2 border-t border-secondary bg-bg px-4 py-3">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ThemeToggle.tsx
|
// ThemeToggle.tsx
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useTheme } from "../hooks/useTheme";
|
import { useTheme } from "../hooks/useTheme";
|
||||||
|
|
||||||
// Actual primary colors for each theme
|
// Actual primary colors for each theme
|
||||||
@@ -13,6 +13,7 @@ const themeColors: Record<string, { primary: string; label: string }> = {
|
|||||||
|
|
||||||
export function ThemeToggle({ compact = false }: { compact?: boolean }) {
|
export function ThemeToggle({ compact = false }: { compact?: boolean }) {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const themes = ["a", "b", "c", "d", "e"] as const;
|
const themes = ["a", "b", "c", "d", "e"] as const;
|
||||||
|
|
||||||
const crossfadeTo = (next: typeof themes[number]) => {
|
const crossfadeTo = (next: typeof themes[number]) => {
|
||||||
@@ -27,6 +28,7 @@ export function ThemeToggle({ compact = false }: { compact?: boolean }) {
|
|||||||
|
|
||||||
// 3) switch theme (your existing logic)
|
// 3) switch theme (your existing logic)
|
||||||
setTheme(next as any);
|
setTheme(next as any);
|
||||||
|
setOpen(false);
|
||||||
|
|
||||||
// 4) remove crossfade flag after the animation
|
// 4) remove crossfade flag after the animation
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
@@ -37,42 +39,46 @@ export function ThemeToggle({ compact = false }: { compact?: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative inline-block text-text">
|
<div className="relative inline-block text-text">
|
||||||
<details className="group">
|
<button
|
||||||
<summary className="cursor-pointer select-none list-none inline-flex items-center gap-2 rounded px-3 py-1.5 bg-secondary/70 hover:bg-secondary focus:outline-none anim-base hover-pop">
|
type="button"
|
||||||
<span className="font-medium">{compact ? "Theme" : "Toggle Theme"}</span>
|
aria-expanded={open}
|
||||||
<span aria-hidden>▾</span>
|
aria-haspopup="listbox"
|
||||||
</summary>
|
className="cursor-pointer select-none inline-flex items-center gap-2 rounded px-3 py-1.5 bg-secondary/70 hover:bg-secondary focus:outline-none anim-base hover-pop"
|
||||||
|
onClick={() => setOpen((v) => !v)}
|
||||||
|
>
|
||||||
|
<span className="font-medium">{compact ? "Theme" : "Toggle Theme"}</span>
|
||||||
|
<span aria-hidden>{open ? "▴" : "▾"}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="
|
className={`
|
||||||
absolute top-full mt-2 z-[70] rounded-lg border border-secondary bg-bg/95 p-2 shadow-xl backdrop-blur
|
absolute top-full mt-2 z-[70] rounded-lg border border-secondary bg-bg/95 p-2 shadow-xl backdrop-blur
|
||||||
left-4 right-4 mx-auto w-[calc(100vw-2rem)] max-w-[18rem]
|
left-4 right-4 mx-auto w-[calc(100vw-2rem)] max-w-[18rem]
|
||||||
md:left-auto md:right-0 md:mx-0 md:w-44 md:max-w-none
|
md:left-auto md:right-0 md:mx-0 md:w-44 md:max-w-none
|
||||||
origin-top scale-y-95 opacity-0 translate-y-[-4px]
|
origin-top transition-[opacity,transform] duration-200 ease-out
|
||||||
pointer-events-none transition-all duration-300 ease-out
|
${open ? "opacity-100 scale-100 translate-y-0 pointer-events-auto" : "opacity-0 scale-95 -translate-y-1 pointer-events-none"}
|
||||||
group-open:opacity-100 group-open:scale-y-100 group-open:translate-y-0 group-open:pointer-events-auto
|
`}
|
||||||
"
|
>
|
||||||
>
|
<ul className="space-y-1" role="listbox">
|
||||||
<ul className="space-y-1">
|
{themes.map((t) => (
|
||||||
{themes.map((t) => (
|
<li key={t}>
|
||||||
<li key={t}>
|
<button
|
||||||
<button
|
type="button"
|
||||||
onClick={() => crossfadeTo(t)}
|
onClick={() => crossfadeTo(t)}
|
||||||
className={`w-full rounded px-3 py-2 text-left hover:bg-secondary/60 anim-base ${
|
className={`w-full rounded px-3 py-2 text-left hover:bg-secondary/60 anim-base ${
|
||||||
theme === t ? "outline outline-1 outline-primary" : ""
|
theme === t ? "outline outline-1 outline-primary" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="mr-2 inline-block h-3 w-3 rounded-full align-middle"
|
className="mr-2 inline-block h-3 w-3 rounded-full align-middle"
|
||||||
style={{ background: themeColors[t].primary }}
|
style={{ background: themeColors[t].primary }}
|
||||||
/>
|
/>
|
||||||
{themeColors[t].label}
|
{themeColors[t].label}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user