Refine navigation states and fix toast centering
CI / quality (push) Waiting to run

This commit is contained in:
MOH
2026-03-10 20:44:38 +01:00
parent 2b7914fc9a
commit 3c53430b12
5 changed files with 89 additions and 22 deletions
+12 -4
View File
@@ -200,23 +200,25 @@ html[lang="ar"] {
[data-sonner-toaster][data-x-position="center"] { [data-sonner-toaster][data-x-position="center"] {
left: 0; left: 0;
right: 0; right: 0;
display: flex;
justify-content: center;
width: 100%; width: 100%;
transform: none; transform: none;
} }
[data-sonner-toaster][data-x-position="center"] [data-sonner-toast] { [data-sonner-toaster][data-x-position="center"] [data-sonner-toast] {
left: 0;
right: 0;
width: fit-content; width: fit-content;
min-width: 0; min-width: 0;
max-width: min(calc(100vw - 2rem), 32rem); max-width: min(calc(100vw - 2rem), 32rem);
margin-inline: auto; margin-inline: 0;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
[data-sonner-toaster][data-x-position="center"] { [data-sonner-toaster][data-x-position="center"] {
left: 0; left: 0;
right: 0; right: 0;
display: flex;
justify-content: center;
width: 100%; width: 100%;
transform: none; transform: none;
} }
@@ -224,7 +226,13 @@ html[lang="ar"] {
[data-sonner-toaster][data-x-position="center"] [data-sonner-toast] { [data-sonner-toaster][data-x-position="center"] [data-sonner-toast] {
width: fit-content; width: fit-content;
max-width: calc(100vw - 2rem); max-width: calc(100vw - 2rem);
margin-inline: auto; margin-inline: 0;
}
[data-sonner-toaster][dir="rtl"][data-x-position="center"] {
left: 0;
right: 0;
transform: none;
} }
} }
+74 -18
View File
@@ -19,12 +19,18 @@ import { cn } from "@/lib/utils";
const navItems = [ const navItems = [
{ key: "home", path: "" }, { key: "home", path: "" },
{ key: "portfolio", path: "/portfolio" },
{ key: "products", path: "/products" },
{ key: "about", path: "/about" }, { key: "about", path: "/about" },
{ key: "portfolio", path: "/portfolio" },
{ key: "products", path: "/products", disabled: true },
{ key: "contact", path: "/contact" }, { key: "contact", path: "/contact" },
]; ];
const disabledDesktopNavItemClassName =
"relative z-10 inline-flex cursor-not-allowed items-center justify-center rounded-pill px-3.5 py-2 text-sm font-medium text-foreground/55 xl:px-4";
const disabledMobileNavItemClassName =
"relative inline-flex cursor-not-allowed items-center justify-center rounded-nested px-4 py-3 text-sm font-medium text-foreground/55";
type SiteHeaderProps = { type SiteHeaderProps = {
isAdmin?: boolean; isAdmin?: boolean;
lightLogoUrl?: string | null; lightLogoUrl?: string | null;
@@ -62,6 +68,7 @@ function NavLinks({
const pathname = usePathname(); const pathname = usePathname();
const t = useTranslations("navigation"); const t = useTranslations("navigation");
const currentPath = stripLocalePrefix(pathname); const currentPath = stripLocalePrefix(pathname);
const isArabic = locale === "ar";
return ( return (
<> <>
@@ -69,6 +76,39 @@ function NavLinks({
(() => { (() => {
const itemPath = item.path || "/"; const itemPath = item.path || "/";
const isActive = isNavItemActive(currentPath, itemPath); const isActive = isNavItemActive(currentPath, itemPath);
const itemLabel = t(item.key);
const soonLabel = t("soon");
if (item.disabled) {
return (
<span
key={item.key}
aria-disabled="true"
className={cn(
mobile ? disabledMobileNavItemClassName : `${disabledDesktopNavItemClassName} group`,
isArabic ? "tracking-normal" : "",
)}
>
{mobile ? (
<span>{itemLabel}</span>
) : (
<>
<span className="transition-opacity duration-150 group-hover:opacity-0 group-focus-within:opacity-0">
{itemLabel}
</span>
<span
className={cn(
"pointer-events-none absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100",
isArabic ? "tracking-normal" : "tracking-[0.08em]",
)}
>
{soonLabel}
</span>
</>
)}
</span>
);
}
return ( return (
<Link <Link
@@ -86,7 +126,7 @@ function NavLinks({
{isActive ? ( {isActive ? (
<span className="absolute inset-0 -z-10 rounded-pill bg-primary shadow-xs" /> <span className="absolute inset-0 -z-10 rounded-pill bg-primary shadow-xs" />
) : null} ) : null}
{t(item.key)} {itemLabel}
</Link> </Link>
); );
})() })()
@@ -348,6 +388,8 @@ export function SiteHeader({
{navItems.map((item) => { {navItems.map((item) => {
const itemPath = item.path || "/"; const itemPath = item.path || "/";
const isActive = isNavItemActive(stripLocalePrefix(pathname), itemPath); const isActive = isNavItemActive(stripLocalePrefix(pathname), itemPath);
const itemLabel = t(item.key);
const isArabic = locale === "ar";
return ( return (
<motion.div <motion.div
@@ -358,21 +400,35 @@ export function SiteHeader({
}} }}
transition={{ duration: 0.18, ease: "easeOut" }} transition={{ duration: 0.18, ease: "easeOut" }}
> >
<Link {item.disabled ? (
href={getLocalizedPath(locale, itemPath)} <div
className={cn( aria-disabled="true"
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors", className="flex cursor-not-allowed items-center justify-between rounded-nested px-4 py-3 text-sm font-medium text-foreground/55"
isActive >
? "bg-accent text-foreground shadow-xs" <div className={cn("inline-flex items-center justify-center", isArabic ? "tracking-normal" : "")}>
: "text-foreground/80 hover:bg-accent/70 hover:text-foreground", <span>{itemLabel}</span>
)} </div>
onClick={() => setIsOpen(false)} <span className="text-xs uppercase tracking-[0.18em] opacity-40">
> 0{navItems.findIndex((navItem) => navItem.key === item.key) + 1}
<span>{t(item.key)}</span> </span>
<span className="text-xs uppercase tracking-[0.18em] opacity-60"> </div>
0{navItems.findIndex((navItem) => navItem.key === item.key) + 1} ) : (
</span> <Link
</Link> href={getLocalizedPath(locale, itemPath)}
className={cn(
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors",
isActive
? "bg-accent text-foreground shadow-xs"
: "text-foreground/80 hover:bg-accent/70 hover:text-foreground",
)}
onClick={() => setIsOpen(false)}
>
<span>{itemLabel}</span>
<span className="text-xs uppercase tracking-[0.18em] opacity-60">
0{navItems.findIndex((navItem) => navItem.key === item.key) + 1}
</span>
</Link>
)}
</motion.div> </motion.div>
); );
})} })}
+1
View File
@@ -5,6 +5,7 @@
"products": "منتجاتي", "products": "منتجاتي",
"about": "معلومات عني", "about": "معلومات عني",
"contact": "تواصل معي", "contact": "تواصل معي",
"soon": "قــريــبــاً",
"root": "Root", "root": "Root",
"openMenu": "فتح القائمة", "openMenu": "فتح القائمة",
"closeMenu": "إغلاق القائمة", "closeMenu": "إغلاق القائمة",
+1
View File
@@ -5,6 +5,7 @@
"products": "Produkte", "products": "Produkte",
"about": "Über mich", "about": "Über mich",
"contact": "Kontakt", "contact": "Kontakt",
"soon": "Bald",
"root": "Root", "root": "Root",
"openMenu": "Menü öffnen", "openMenu": "Menü öffnen",
"closeMenu": "Menü schliessen", "closeMenu": "Menü schliessen",
+1
View File
@@ -5,6 +5,7 @@
"products": "Products", "products": "Products",
"about": "About Me", "about": "About Me",
"contact": "Contact", "contact": "Contact",
"soon": "Soon",
"root": "Root", "root": "Root",
"openMenu": "Open menu", "openMenu": "Open menu",
"closeMenu": "Close menu", "closeMenu": "Close menu",