+
+
+
+
diff --git a/src/components/global/TopNav.tsx b/src/components/global/TopNav.tsx
index f84ac95..1e33623 100644
--- a/src/components/global/TopNav.tsx
+++ b/src/components/global/TopNav.tsx
@@ -1,9 +1,9 @@
"use client"
import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
-import { Menu } from "lucide-react";
+import { ChevronDown, Ellipsis, Menu } from "lucide-react";
import Link from "next/link";
-import { useState } from "react";
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Button } from "../ui/button";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "../ui/sheet";
@@ -18,6 +18,7 @@ const links = [
{ href: "/artworks/artfight", label: "Artfight" }
],
},
+ { type: "link" as const, href: "/miniatures", label: "Miniatures" },
{ type: "link" as const, href: "/commissions", label: "Commissions" },
{ type: "link" as const, href: "/commissions/status", label: "Commission Status" },
{ type: "link" as const, href: "/tos", label: "Terms of Service" },
@@ -26,46 +27,196 @@ const links = [
export default function TopNav() {
const [open, setOpen] = useState(false)
+ const requiredLinks = [
+ links[0], // Home
+ links[1], // Portfolio
+ links[4], // Commissions
+ ];
+ const flexibleLinks = links.filter((link) => !requiredLinks.includes(link));
+ const [visibleCount, setVisibleCount] = useState(flexibleLinks.length);
+ const listRef = useRef
(null);
+ const containerRef = useRef(null);
+ const measureListRef = useRef(null);
+ const [containerWidth, setContainerWidth] = useState(0);
+
+ const visibleLinks = [...requiredLinks, ...flexibleLinks.slice(0, visibleCount)];
+ const overflowLinks = flexibleLinks.slice(visibleCount);
+ const showMore = overflowLinks.length > 0;
+
+ useEffect(() => {
+ const containerEl = containerRef.current;
+ if (!containerEl) return;
+ const update = () => {
+ setContainerWidth(containerEl.getBoundingClientRect().width);
+ };
+ const ro = new ResizeObserver(update);
+ ro.observe(containerEl);
+ window.addEventListener("resize", update);
+ update();
+ return () => {
+ ro.disconnect();
+ window.removeEventListener("resize", update);
+ };
+ }, []);
+
+ useLayoutEffect(() => {
+ const measureEl = measureListRef.current;
+ if (!measureEl || !containerWidth) return;
+ const items = Array.from(measureEl.children) as HTMLElement[];
+ if (!items.length) return;
+
+ const moreItem = items[items.length - 1];
+ const moreWidth = moreItem.getBoundingClientRect().width;
+ const itemWidths = items.slice(0, -1).map((el) => el.getBoundingClientRect().width);
+
+ const requiredIndexes = new Set([0, 1, 4]);
+ let requiredWidth = 0;
+ const flexibleWidths: number[] = [];
+ itemWidths.forEach((w, idx) => {
+ if (requiredIndexes.has(idx)) {
+ requiredWidth += w;
+ } else {
+ flexibleWidths.push(w);
+ }
+ });
+
+ const totalFlexibleWidth = flexibleWidths.reduce((a, b) => a + b, 0);
+ let nextVisibleCount = flexibleLinks.length;
+ const safetyPadding = 24;
+
+ if (requiredWidth + totalFlexibleWidth + safetyPadding > containerWidth) {
+ let available = containerWidth - requiredWidth - moreWidth - safetyPadding;
+ if (available < 0) available = 0;
+ let fit = 0;
+ let used = 0;
+ for (const w of flexibleWidths) {
+ if (used + w > available) break;
+ used += w;
+ fit += 1;
+ }
+ nextVisibleCount = fit;
+ }
+
+ if (nextVisibleCount !== visibleCount) {
+ setVisibleCount(nextVisibleCount);
+ }
+
+ }, [containerWidth, flexibleLinks.length, visibleCount]);
return (
{/* Desktop Nav */}
-
-
-
- {links.map((item) => {
- if (item.type === "dropdown") {
+
+
+
+
+ {visibleLinks.map((item) => {
+ if (item.type === "dropdown") {
+ return (
+
+
+ {item.label}
+
+
+
+ {item.items.map(({ href, label }) => (
+ -
+
+ {label}
+
+
+ ))}
+
+
+
+ );
+ }
+
return (
-
-
- {item.label}
-
-
-
- {item.items.map(({ href, label }) => (
- -
-
- {label}
-
-
- ))}
-
-
+
+
+ {item.label}
+
);
- }
+ })}
+ {showMore ? (
+
+
+
+
+
+
+ {overflowLinks.map((item) => {
+ if (item.type === "dropdown") {
+ return (
+ -
+
+ {item.label}
+
+
+ {item.items.map(({ href, label }) => (
+ -
+
+ {label}
+
+
+ ))}
+
+
+ );
+ }
- return (
-
-
- {item.label}
-
+ return (
+ -
+
+ {item.label}
+
+
+ );
+ })}
+
+
- );
- })}
+ ) : null}
+
+
+
+
+
+
+
+
+ {links.map((item) => (
+
+
+ {item.type === "dropdown" ? (
+
+ {item.label}
+
+
+ ) : (
+ item.label
+ )}
+
+
+ ))}
+
+
+
+
+
diff --git a/src/components/global/UnderConstruction.tsx b/src/components/global/UnderConstruction.tsx
new file mode 100644
index 0000000..0120822
--- /dev/null
+++ b/src/components/global/UnderConstruction.tsx
@@ -0,0 +1,55 @@
+import { cn } from "@/lib/utils";
+import { Construction } from "lucide-react";
+import Link from "next/link";
+
+export default function UnderConstruction({
+ title = "Under Construction",
+ subtitle = "Artfight is getting its finishing touches.",
+ note = "Check back soon for the full gallery.",
+ actionHref = "/artworks",
+ actionLabel = "Back to Portfolio",
+}: {
+ title?: string;
+ subtitle?: string;
+ note?: string;
+ actionHref?: string;
+ actionLabel?: string;
+}) {
+ return (
+
+
+
+
+
+
+
+
+ Under construction
+
+
+ {title}
+
+
+ {subtitle}
+
+
{note}
+
+ {actionLabel}
+
+
+
+
+
+
+ );
+}