diff --git a/src/app/(normal)/artworks/artfight/page.tsx b/src/app/(normal)/artworks/artfight/page.tsx new file mode 100644 index 0000000..8e67e87 --- /dev/null +++ b/src/app/(normal)/artworks/artfight/page.tsx @@ -0,0 +1,13 @@ +import UnderConstruction from "@/components/global/UnderConstruction"; + +export default function ArtfightPage() { + return ( +
+ +
+ ); +} diff --git a/src/app/(normal)/miniatures/page.tsx b/src/app/(normal)/miniatures/page.tsx new file mode 100644 index 0000000..2e0ee0d --- /dev/null +++ b/src/app/(normal)/miniatures/page.tsx @@ -0,0 +1,13 @@ +import UnderConstruction from "@/components/global/UnderConstruction"; + +export default function MiniaturesPage() { + return ( +
+ +
+ ); +} diff --git a/src/components/global/Header.tsx b/src/components/global/Header.tsx index e6b8a18..6f51c27 100644 --- a/src/components/global/Header.tsx +++ b/src/components/global/Header.tsx @@ -6,9 +6,11 @@ export default function Header() { return (
-
- -
+
+
+ +
+
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} + +
+ +
+
+ +
+
+
+
+ ); +}