mirror of
https://github.com/Ategon/Jamjar.git
synced 2025-02-12 06:16:21 +00:00
Add prefers reduced motion handling
This commit is contained in:
parent
feb80916cc
commit
b11ad9354b
7 changed files with 156 additions and 16 deletions
|
@ -1,5 +1,7 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@nextui-org/react";
|
import { Button } from "@nextui-org/react";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
interface ButtonActionProps {
|
interface ButtonActionProps {
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
|
@ -12,10 +14,28 @@ export default function ButtonAction({
|
||||||
onPress,
|
onPress,
|
||||||
name,
|
name,
|
||||||
}: ButtonActionProps) {
|
}: ButtonActionProps) {
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
endContent={icon}
|
endContent={icon}
|
||||||
className="text-[#333] dark:text-white border-[#333]/50 dark:border-white/50 hover:scale-110 transition-all transform duration-500 ease-in-out"
|
className={`text-[#333] dark:text-white border-[#333]/50 dark:border-white/50 transition-all transform duration-500 ease-in-out ${
|
||||||
|
!reduceMotion ? "hover:scale-110" : ""
|
||||||
|
}`}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { Button, Link } from "@nextui-org/react";
|
import { Button, Link } from "@nextui-org/react";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
interface ButtonLinkProps {
|
interface ButtonLinkProps {
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
|
@ -8,14 +10,34 @@ interface ButtonLinkProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ButtonLink({ icon, href, name }: ButtonLinkProps) {
|
export default function ButtonLink({ icon, href, name }: ButtonLinkProps) {
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110"
|
className={`flex justify-center duration-500 ease-in-out transition-all transform ${
|
||||||
|
!reduceMotion ? "hover:scale-110" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
endContent={icon}
|
endContent={icon}
|
||||||
className="text-[#333] dark:text-white border-[#333]/50 dark:border-white/50 transition-all transform !duration-500 ease-in-out"
|
className={`text-[#333] dark:text-white border-[#333]/50 dark:border-white/50 transition-all transform !duration-500 ease-in-out ${
|
||||||
|
!reduceMotion ? "hover:scale-110" : ""
|
||||||
|
}`}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { Link } from "@nextui-org/react";
|
import { Link } from "@nextui-org/react";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
interface IconLinkProps {
|
interface IconLinkProps {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
@ -7,10 +9,28 @@ interface IconLinkProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function IconLink({ icon, href }: IconLinkProps) {
|
export default function IconLink({ icon, href }: IconLinkProps) {
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="text-[#333] dark:text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 duration-500 ease-in-out transition-color"
|
className={`text-[#333] dark:text-white flex justify-center duration-500 ease-in-out transition-all transform ${
|
||||||
|
!reduceMotion ? "hover:scale-125" : ""
|
||||||
|
} transition-color`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { Link as BaseLink } from "@nextui-org/react";
|
import { Link as BaseLink } from "@nextui-org/react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface LinkProps {
|
interface LinkProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -6,10 +9,28 @@ interface LinkProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Link({ name, href }: LinkProps) {
|
export default function Link({ name, href }: LinkProps) {
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseLink
|
<BaseLink
|
||||||
href={href}
|
href={href}
|
||||||
className="text-[#333] dark:text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110 duration-500 ease-in-out transition-color"
|
className={`text-[#333] dark:text-white flex justify-center duration-500 ease-in-out transition-all transform ${
|
||||||
|
!reduceMotion ? "hover:scale-110" : ""
|
||||||
|
} transition-color`}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</BaseLink>
|
</BaseLink>
|
||||||
|
|
|
@ -38,6 +38,21 @@ export default function PCNavbar() {
|
||||||
const [jam, setJam] = useState<JamType | null>();
|
const [jam, setJam] = useState<JamType | null>();
|
||||||
const [isInJam, setIsInJam] = useState<boolean>();
|
const [isInJam, setIsInJam] = useState<boolean>();
|
||||||
const [user, setUser] = useState<UserType>();
|
const [user, setUser] = useState<UserType>();
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUser();
|
loadUser();
|
||||||
|
@ -91,7 +106,9 @@ export default function PCNavbar() {
|
||||||
<NavbarBrand className="flex-grow-0">
|
<NavbarBrand className="flex-grow-0">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="duration-500 ease-in-out transition-all transform hover:scale-110"
|
className={`duration-500 ease-in-out transition-all transform ${
|
||||||
|
reduceMotion ? "" : "hover:scale-110"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
as={NextImage}
|
as={NextImage}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Heart, LoaderCircle } from "lucide-react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
import { getCookie } from "@/helpers/cookie";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
export default function LikeButton({ post }: { post: PostType }) {
|
export default function LikeButton({ post }: { post: PostType }) {
|
||||||
|
@ -14,6 +14,21 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [liked, setLiked] = useState<boolean>(false);
|
const [liked, setLiked] = useState<boolean>(false);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -56,7 +71,7 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
if (response.status == 401) {
|
if (response.status == 401) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
} else {
|
} else {
|
||||||
toast.error("An error occured");
|
toast.error("An error occurred");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,7 +94,11 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
<Heart size={16} />
|
<Heart size={16} />
|
||||||
<Heart
|
<Heart
|
||||||
size={16}
|
size={16}
|
||||||
className={liked ? "animate-ping absolute top-0 left-0" : ""}
|
className={
|
||||||
|
liked && !reduceMotion
|
||||||
|
? "animate-ping absolute top-0 left-0"
|
||||||
|
: "absolute top-0 left-0"
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "0",
|
top: "0",
|
||||||
|
|
|
@ -8,16 +8,31 @@ export default function ThemeToggle() {
|
||||||
const [isSpinning, setIsSpinning] = useState<boolean>(false);
|
const [isSpinning, setIsSpinning] = useState<boolean>(false);
|
||||||
const { setTheme, resolvedTheme } = useTheme();
|
const { setTheme, resolvedTheme } = useTheme();
|
||||||
const [mounted, setMounted] = useState<boolean>(false);
|
const [mounted, setMounted] = useState<boolean>(false);
|
||||||
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
setReduceMotion(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setReduceMotion(event.matches);
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (isSpinning) return;
|
if (isSpinning) return;
|
||||||
|
|
||||||
setIsSpinning(true);
|
if (!reduceMotion) {
|
||||||
setTimeout(() => setIsSpinning(false), 500);
|
setIsSpinning(true);
|
||||||
|
setTimeout(() => setIsSpinning(false), 500);
|
||||||
|
}
|
||||||
|
|
||||||
setTheme(resolvedTheme === "dark" ? "light" : "dark");
|
setTheme(resolvedTheme === "dark" ? "light" : "dark");
|
||||||
};
|
};
|
||||||
|
@ -30,9 +45,15 @@ export default function ThemeToggle() {
|
||||||
<div
|
<div
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
className={`${isSpinning && "animate-[spin_0.5s_ease-out]"} `}
|
className={`${
|
||||||
|
isSpinning && !reduceMotion ? "animate-[spin_0.5s_ease-out]" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="!duration-250 !ease-linear !transition-all transform text-[#333] dark:text-white hover:scale-125">
|
<div
|
||||||
|
className={`!duration-250 !ease-linear !transition-all transform text-[#333] dark:text-white ${
|
||||||
|
!reduceMotion ? "hover:scale-125" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{resolvedTheme === "dark" && <Moon />}
|
{resolvedTheme === "dark" && <Moon />}
|
||||||
{resolvedTheme === "light" && <Sun />}
|
{resolvedTheme === "light" && <Sun />}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue