feat: add Moon2 icon component and update theme toggle with dropdown menu

refactor: adjust styling for navbar components and remove settings modal
i18n: add theme translations for light, dark, and system modes
This commit is contained in:
Marco Pedone 2026-02-24 11:07:39 +01:00
parent fd7da28535
commit 055fcc230e
11 changed files with 140 additions and 76 deletions

View file

@ -378,3 +378,22 @@ const CheckCircleIcon = ({ className }: { className?: string }) => (
/> />
</svg> </svg>
); );
export const Moon2 = ({ className }: { className?: string }) => (
<svg
className={className}
fill="none"
height="15"
viewBox="0 0 15 15"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<title>Moon</title>
<path
clip-rule="evenodd"
d="M2.89998 0.499976C2.89998 0.279062 2.72089 0.0999756 2.49998 0.0999756C2.27906 0.0999756 2.09998 0.279062 2.09998 0.499976V1.09998H1.49998C1.27906 1.09998 1.09998 1.27906 1.09998 1.49998C1.09998 1.72089 1.27906 1.89998 1.49998 1.89998H2.09998V2.49998C2.09998 2.72089 2.27906 2.89998 2.49998 2.89998C2.72089 2.89998 2.89998 2.72089 2.89998 2.49998V1.89998H3.49998C3.72089 1.89998 3.89998 1.72089 3.89998 1.49998C3.89998 1.27906 3.72089 1.09998 3.49998 1.09998H2.89998V0.499976ZM5.89998 3.49998C5.89998 3.27906 5.72089 3.09998 5.49998 3.09998C5.27906 3.09998 5.09998 3.27906 5.09998 3.49998V4.09998H4.49998C4.27906 4.09998 4.09998 4.27906 4.09998 4.49998C4.09998 4.72089 4.27906 4.89998 4.49998 4.89998H5.09998V5.49998C5.09998 5.72089 5.27906 5.89998 5.49998 5.89998C5.72089 5.89998 5.89998 5.72089 5.89998 5.49998V4.89998H6.49998C6.72089 4.89998 6.89998 4.72089 6.89998 4.49998C6.89998 4.27906 6.72089 4.09998 6.49998 4.09998H5.89998V3.49998ZM1.89998 6.49998C1.89998 6.27906 1.72089 6.09998 1.49998 6.09998C1.27906 6.09998 1.09998 6.27906 1.09998 6.49998V7.09998H0.499976C0.279062 7.09998 0.0999756 7.27906 0.0999756 7.49998C0.0999756 7.72089 0.279062 7.89998 0.499976 7.89998H1.09998V8.49998C1.09998 8.72089 1.27906 8.89997 1.49998 8.89997C1.72089 8.89997 1.89998 8.72089 1.89998 8.49998V7.89998H2.49998C2.72089 7.89998 2.89998 7.72089 2.89998 7.49998C2.89998 7.27906 2.72089 7.09998 2.49998 7.09998H1.89998V6.49998ZM8.54406 0.98184L8.24618 0.941586C8.03275 0.917676 7.90692 1.1655 8.02936 1.34194C8.17013 1.54479 8.29981 1.75592 8.41754 1.97445C8.91878 2.90485 9.20322 3.96932 9.20322 5.10022C9.20322 8.37201 6.82247 11.0878 3.69887 11.6097C3.45736 11.65 3.20988 11.6772 2.96008 11.6906C2.74563 11.702 2.62729 11.9535 2.77721 12.1072C2.84551 12.1773 2.91535 12.2458 2.98667 12.3128L3.05883 12.3795L3.31883 12.6045L3.50684 12.7532L3.62796 12.8433L3.81491 12.9742L3.99079 13.089C4.11175 13.1651 4.23536 13.2375 4.36157 13.3059L4.62496 13.4412L4.88553 13.5607L5.18837 13.6828L5.43169 13.7686C5.56564 13.8128 5.70149 13.8529 5.83857 13.8885C5.94262 13.9155 6.04767 13.9401 6.15405 13.9622C6.27993 13.9883 6.40713 14.0109 6.53544 14.0298L6.85241 14.0685L7.11934 14.0892C7.24637 14.0965 7.37436 14.1002 7.50322 14.1002C11.1483 14.1002 14.1032 11.1453 14.1032 7.50023C14.1032 7.25044 14.0893 7.00389 14.0623 6.76131L14.0255 6.48407C13.991 6.26083 13.9453 6.04129 13.8891 5.82642C13.8213 5.56709 13.7382 5.31398 13.6409 5.06881L13.5279 4.80132L13.4507 4.63542L13.3766 4.48666C13.2178 4.17773 13.0353 3.88295 12.8312 3.60423L12.6782 3.40352L12.4793 3.16432L12.3157 2.98361L12.1961 2.85951L12.0355 2.70246L11.8134 2.50184L11.4925 2.24191L11.2483 2.06498L10.9562 1.87446L10.6346 1.68894L10.3073 1.52378L10.1938 1.47176L9.95488 1.3706L9.67791 1.2669L9.42566 1.1846L9.10075 1.09489L8.83599 1.03486L8.54406 0.98184ZM10.4032 5.30023C10.4032 4.27588 10.2002 3.29829 9.83244 2.40604C11.7623 3.28995 13.1032 5.23862 13.1032 7.50023C13.1032 10.593 10.596 13.1002 7.50322 13.1002C6.63646 13.1002 5.81597 12.9036 5.08355 12.5522C6.5419 12.0941 7.81081 11.2082 8.74322 10.0416C8.87963 10.2284 9.10028 10.3497 9.34928 10.3497C9.76349 10.3497 10.0993 10.0139 10.0993 9.59971C10.0993 9.24256 9.84965 8.94373 9.51535 8.86816C9.57741 8.75165 9.63653 8.63334 9.6926 8.51332C9.88358 8.63163 10.1088 8.69993 10.35 8.69993C11.0403 8.69993 11.6 8.14028 11.6 7.44993C11.6 6.75976 11.0406 6.20024 10.3505 6.19993C10.3853 5.90487 10.4032 5.60464 10.4032 5.30023Z"
fill="currentColor"
fill-rule="evenodd"
></path>
</svg>
);

View file

@ -83,7 +83,7 @@ export const UserHeaderSection = ({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
aria-label="User Profile Button" aria-label="User Profile Button"
className="flex h-10 w-full items-center justify-center gap-x-1 rounded-full py-2 pr-0 pl-0 font-medium md:pr-2" className="flex h-10 w-full items-center justify-center gap-x-1 rounded-full py-2 navqry:pr-2 pr-0 pl-0 font-medium"
variant="ghost" variant="ghost"
> >
<UserAvatar <UserAvatar

View file

@ -7,7 +7,7 @@ export function MainNav() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="mr-2 hidden gap-6 md:flex"> <div className="hidden gap-2 md:flex lg:gap-4">
<Link <Link
aria-label="mainNavLink" aria-label="mainNavLink"
className="flex shrink-0 items-center font-medium md:mb-0" className="flex shrink-0 items-center font-medium md:mb-0"

View file

@ -26,7 +26,12 @@ export function RicercaCommand() {
<> <>
<Dialog onOpenChange={setOpen} open={open}> <Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button aria-label="ricercaBtn" className="p-2" variant="ghost"> <Button
aria-label="ricercaBtn"
className="p-2"
size="sm"
variant="outline"
>
<Search /> <Search />
</Button> </Button>
</DialogTrigger> </DialogTrigger>

View file

@ -1,26 +0,0 @@
import { Settings } from "lucide-react";
import { LangToggle } from "~/components/navbar/lang-toggle";
import { Button } from "~/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
import { ThemeSwitch } from "./theme-toggle";
export const SettingsPopover = () => {
return (
<Popover>
<PopoverTrigger asChild>
<Button aria-label="Settings" className="p-2" variant="ghost">
<Settings />
</Button>
</PopoverTrigger>
<PopoverContent className="flex w-fit justify-center gap-4 p-4">
<LangToggle />
<ThemeSwitch />
</PopoverContent>
</Popover>
);
};

View file

@ -5,7 +5,8 @@ import { UserHeaderSection } from "~/components/navbar/login-button";
import { MainNav } from "~/components/navbar/main-nav"; import { MainNav } from "~/components/navbar/main-nav";
import { MobileNav } from "~/components/navbar/mobile-nav"; import { MobileNav } from "~/components/navbar/mobile-nav";
import { RicercaCommand } from "~/components/navbar/ricerca_command"; import { RicercaCommand } from "~/components/navbar/ricerca_command";
import { SettingsPopover } from "./settings-modal"; import { LangToggle } from "./lang-toggle";
import { ThemeSwitch } from "./theme-toggle";
export function SiteHeader() { export function SiteHeader() {
const pathname = usePathname(); const pathname = usePathname();
@ -22,7 +23,7 @@ export function SiteHeader() {
}; };
return ( return (
<header className="sticky top-0 z-50 w-full border-muted border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60"> <header className="sticky top-0 z-50 w-full border-muted border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60">
<div className="mx-auto flex h-14 w-full items-center pr-3 pl-4 md:pr-4 md:pl-8"> <div className="mx-auto flex h-14 w-full items-center gap-2 pr-3 pl-4 md:pr-4 md:pl-5">
<MainNav /> <MainNav />
<MobileNav <MobileNav
open={openSidebar} open={openSidebar}
@ -32,11 +33,12 @@ export function SiteHeader() {
userOpen={openUserMenuSidebar} userOpen={openUserMenuSidebar}
/> />
<div className="float-end flex flex-1 items-center justify-end"> <div className="float-end flex flex-1 items-center justify-end gap-2">
<RicercaCommand /> <RicercaCommand />
<div className="hidden w-fit items-center gap-2 md:flex"> <div className="hidden w-fit items-center gap-2 md:flex">
<SettingsPopover /> <LangToggle />
<ThemeSwitch />
</div> </div>
<UserHeaderSection <UserHeaderSection
key={pathname} key={pathname}

View file

@ -1,8 +1,16 @@
import { motion, type Transition } from "framer-motion"; import { Sun } from "lucide-react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { cn } from "~/lib/utils"; import { useTranslation } from "~/providers/I18nProvider";
import { Moon2 } from "../IconComponents";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
export const ThemeSwitch = () => { export const ThemeSwitch = () => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@ -14,7 +22,7 @@ export const ThemeSwitch = () => {
return <ThemeSwitchSkeleton />; return <ThemeSwitchSkeleton />;
} }
return <ThemeSwitchContent />; return <ThemeSwitchContent2 />;
}; };
const ThemeSwitchSkeleton = () => ( const ThemeSwitchSkeleton = () => (
@ -24,51 +32,89 @@ const ThemeSwitchSkeleton = () => (
/> />
); );
const ThemeSwitchContent = () => { // const ThemeSwitchContent = () => {
// const { theme, setTheme } = useTheme();
// const spring: Transition = {
// damping: 20,
// stiffness: 300,
// type: "spring",
// };
// return (
// <button
// aria-label="Toggle Theme"
// className={cn(
// `flex h-7 w-12 items-center rounded-full bg-muted p-1.25 shadow-inner hover:cursor-pointer`,
// theme === "light"
// ? "place-content-end bg-sky-400 hover:bg-sky-500"
// : "bg-sky-900 hover:bg-sky-800",
// )}
// onClick={() => setTheme(theme === "light" ? "dark" : "light")}
// type="button"
// >
// <motion.div
// //animate={{ opacity: 1 }}
// className={cn(
// "flex size-5.5 items-center justify-center rounded-full bg-black",
// theme === "light" ? "bg-yellow-300" : "bg-gray-800/60",
// )}
// //initial={{ opacity: 0 }}
// layout
// transition={{
// //opacity: { duration: 0.1, ease: "easeInOut", delay: 0.5 }, // Fast fade in
// layout: spring, // Spring for position change
// }}
// >
// <motion.div
// animate={{ rotate: theme === "light" ? 180 : 0 }}
// transition={spring}
// >
// {theme === "light" ? (
// <Sun className="size-5" />
// ) : (
// <Moon className="size-5 fill-yellow-200 stroke-yellow-200" />
// )}
// </motion.div>
// </motion.div>
// </button>
// );
// };
const ThemeSwitchContent2 = () => {
const { theme, setTheme } = useTheme(); const { theme, setTheme } = useTheme();
const spring: Transition = { const { t } = useTranslation();
damping: 20,
stiffness: 300,
type: "spring",
};
return ( return (
<button <DropdownMenu>
aria-label="Toggle Theme" <DropdownMenuTrigger asChild>
className={cn( <Button
`flex h-7 w-12 items-center rounded-full bg-muted p-1.25 shadow-inner hover:cursor-pointer`, className="p-2"
theme === "light"
? "place-content-end bg-sky-400 hover:bg-sky-500"
: "bg-sky-900 hover:bg-sky-800",
)}
onClick={() => setTheme(theme === "light" ? "dark" : "light")} onClick={() => setTheme(theme === "light" ? "dark" : "light")}
type="button" size="sm"
> variant="outline"
<motion.div
//animate={{ opacity: 1 }}
className={cn(
"flex size-5.5 items-center justify-center rounded-full bg-black",
theme === "light" ? "bg-yellow-300" : "bg-gray-800/60",
)}
//initial={{ opacity: 0 }}
layout
transition={{
//opacity: { duration: 0.1, ease: "easeInOut", delay: 0.5 }, // Fast fade in
layout: spring, // Spring for position change
}}
>
<motion.div
animate={{ rotate: theme === "light" ? 180 : 0 }}
transition={spring}
> >
{theme === "light" ? ( {theme === "light" ? (
<Sun className="size-5 fill-yellow-500 stroke-yellow-500" /> <Sun className="size-5" />
) : ( ) : (
<Moon className="size-5 fill-yellow-200 stroke-yellow-200" /> <Moon2 className="size-5" />
)} )}
</motion.div> </Button>
</motion.div> </DropdownMenuTrigger>
</button> <DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => setTheme("light")}>
{t.theme.light}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
{t.theme.dark}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
{t.theme.system}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
); );
}; };

View file

@ -184,6 +184,11 @@ export const en: LangDict = {
accedi_per_procedere: "Log in to proceed", accedi_per_procedere: "Log in to proceed",
accetta: "Accept", accetta: "Accept",
acconto: "Dwn. Paym.", acconto: "Dwn. Paym.",
theme: {
light: "Light",
dark: "Dark",
system: "System",
},
account: { account: {
cognome: "Surname", cognome: "Surname",
email: "Email", email: "Email",

View file

@ -184,6 +184,11 @@ export const it: LangDict = {
sottotitolo: "Un errore interno, riprova più tardi", sottotitolo: "Un errore interno, riprova più tardi",
titolo: "Errore imprevisto", titolo: "Errore imprevisto",
}, },
theme: {
light: "Chiaro",
dark: "Scuro",
system: "Sistema",
},
accedi_per_procedere: "Accedi per procedere", accedi_per_procedere: "Accedi per procedere",
accetta: "Accetta", accetta: "Accetta",
acconto: "Acconto", acconto: "Acconto",

View file

@ -71,6 +71,11 @@ export type LangDict = {
contatti: string; contatti: string;
guida: string; guida: string;
prezzi_titolo: string; prezzi_titolo: string;
theme: {
light: string;
dark: string;
system: string;
};
notifiche: { notifiche: {
titolo_popup: string; titolo_popup: string;

View file

@ -162,7 +162,10 @@ const Contatto: NextPage = () => {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<div className="flex flex-wrap items-center gap-x-2"> <div className="flex flex-wrap items-center gap-x-2">
<FormLabel className="font-medium text-base"> <FormLabel
className="font-medium text-base"
htmlFor="numero"
>
{t.contact_form.telefono} {t.contact_form.telefono}
</FormLabel> </FormLabel>
<FormMessage /> <FormMessage />