diff --git a/src/app/page.tsx b/src/app/page.tsx index 57b2822f..ebe274ac 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -28,6 +28,8 @@ import { ZamaIcon, } from "@/components/icons"; import { DefenderIcon } from "@/components/icons/defender-icon"; +import { latestStable as monitorLatestStable } from "../../content/monitor/latest-versions"; +import { latestStable as relayerLatestStable } from "../../content/relayer/latest-versions"; import { baseOptions } from "./layout.config"; export default function HomePage() { @@ -137,7 +139,7 @@ export default function HomePage() { {/* Dual Heroes: Monitor and Relayer */}
} title="Relayer" description="Automate onchain transactions to schedule jobs, batch calls, and relay gasless meta transactions within your self-hosted infrastructure." @@ -145,7 +147,7 @@ export default function HomePage() { /> } title="Monitor" description="Monitor onchain activity in real time to watch critical events, detect anomalies, trigger alerts on your preferred channels, and set automated responses with Relayer." diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 1f95d544..550a5276 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -236,7 +236,7 @@ export function SidebarFooter(props: ComponentProps<"div">) { export function SidebarViewport(props: ScrollAreaProps) { return ( - + (({ className, children, ...props }, ref) => ( @@ -43,14 +43,16 @@ const ScrollBar = React.forwardRef< ref={ref} orientation={orientation} className={cn( - "flex select-none data-[state=hidden]:animate-fd-fade-out", - orientation === "vertical" && "h-full w-1.5", - orientation === "horizontal" && "h-1.5 flex-col", + "flex select-none touch-none p-0.5 transition-colors", + orientation === "vertical" && + "h-full w-2.5 border-l border-l-transparent", + orientation === "horizontal" && + "h-2.5 flex-col border-t border-t-transparent", className, )} {...props} > - + )); ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName; diff --git a/src/lib/is-active.ts b/src/lib/is-active.ts index de94c090..78164fc3 100644 --- a/src/lib/is-active.ts +++ b/src/lib/is-active.ts @@ -5,6 +5,59 @@ function normalize(url: string) { return url; } +// Products that have versioned paths like /product/X.Y.x/... +const VERSIONED_PRODUCTS = ["/relayer", "/monitor"]; + +// Version pattern: matches patterns like "1.3.x", "1.0.x", "2.1.x" +const VERSION_PATTERN = /^(\d+\.\d+\.x)$/; + +interface ParsedPath { + base: string; + version: string | null; // null means development/unversioned + subpath: string; +} + +/** + * Parses a path for versioned products. + * For "/relayer/1.3.x/quickstart" returns { base: "/relayer", version: "1.3.x", subpath: "/quickstart" } + * For "/relayer/quickstart" (development) returns { base: "/relayer", version: null, subpath: "/quickstart" } + * For "/relayer" (development root) returns { base: "/relayer", version: null, subpath: "" } + * For non-product paths, returns null. + */ +function parseProductPath(path: string): ParsedPath | null { + const normalizedPath = normalize(path); + + for (const product of VERSIONED_PRODUCTS) { + if (normalizedPath === product) { + // Exact match to product root (development version) + return { base: product, version: null, subpath: "" }; + } + + if (normalizedPath.startsWith(`${product}/`)) { + const remainder = normalizedPath.slice(product.length + 1); // +1 for the slash + const segments = remainder.split("/"); + const firstSegment = segments[0]; + + if (firstSegment && VERSION_PATTERN.test(firstSegment)) { + // Versioned path: /relayer/1.3.x or /relayer/1.3.x/quickstart + return { + base: product, + version: firstSegment, + subpath: segments.length > 1 ? `/${segments.slice(1).join("/")}` : "", + }; + } + // Development path with subpath: /relayer/quickstart + return { + base: product, + version: null, + subpath: `/${remainder}`, + }; + } + } + + return null; +} + export function isActive( url: string, pathname: string, @@ -13,7 +66,39 @@ export function isActive( url = normalize(url); pathname = normalize(pathname); - return url === pathname || (nested && pathname.startsWith(`${url}/`)); + // Standard exact match or nested match + if (url === pathname || (nested && pathname.startsWith(`${url}/`))) { + return true; + } + + // Check for versioned/development path matching + const urlParsed = parseProductPath(url); + const pathnameParsed = parseProductPath(pathname); + + if (urlParsed && pathnameParsed) { + // Both are paths for the same product + if (urlParsed.base === pathnameParsed.base) { + // Same subpath means it's the same "page" just different version + if (urlParsed.subpath === pathnameParsed.subpath) { + return true; + } + + // Check if pathname is nested under url's subpath + if ( + nested && + pathnameParsed.subpath.startsWith(`${urlParsed.subpath}/`) + ) { + return true; + } + + // Handle root match: /relayer/1.3.x should match any /relayer/* when nested + if (nested && urlParsed.subpath === "") { + return true; + } + } + } + + return false; } export function isTabActive(tab: SidebarTab, pathname: string) {