Files
community-rule/app/components/navigation/Top/Top.view.tsx
T
adilalloandCursor 5242f8c6bb Give the shell one header, a skip link, and a cookie-aware first paint.
Breakpoint clones were still in the tab order, marketing always painted Log in, and at 430px the logo overlay covered Use cases and Learn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 17:12:59 -06:00

162 lines
6.7 KiB
TypeScript

"use client";
import { memo } from "react";
import Script from "next/script";
import { useTranslation } from "../../../contexts/MessagesContext";
import { ASSETS, getAssetPath } from "../../../../lib/assetUtils";
import Menu from "../Menu";
import type { TopViewProps } from "./Top.types";
import Logo from "../../asset/Logo";
function TopView({
folderTop,
loggedIn: _loggedIn,
profile: _profile,
logIn,
schemaData,
logoSize,
renderNavigationItems,
renderLoginButton,
renderCreateRuleButton,
}: TopViewProps) {
const t = useTranslation(folderTop ? "homeHeader" : "header");
const loginControl = logIn ? (
<Menu size="X Small" className="lg:gap-[var(--spacing-scale-012)]">
{renderLoginButton()}
</Menu>
) : null;
if (folderTop) {
return (
<>
<Script
id="top-navigation-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>
<header
className="w-full overflow-hidden bg-transparent"
role="banner"
aria-label={t("ariaLabels.homePageNavigationHeader")}
>
<nav
className="relative mx-auto flex h-[50px] items-end justify-between gap-[var(--spacing-scale-008)] pl-[var(--spacing-scale-008)] pr-[var(--spacing-scale-016)] pt-[var(--spacing-scale-010)] sm:h-[62px] sm:px-[var(--spacing-scale-010)] sm:pr-[var(--spacing-scale-020)] sm:pt-[var(--spacing-scale-010)] md:h-[68px] md:px-[var(--spacing-scale-016)] md:pr-[var(--spacing-scale-032)] md:pt-[var(--spacing-scale-016)] lg:h-[68px] lg:pl-[var(--spacing-scale-024)] lg:pr-[var(--spacing-scale-056)] lg:pt-[var(--spacing-scale-016)] xl:h-[88px] xl:pl-[var(--spacing-scale-048)] xl:pr-[var(--spacing-scale-056)] xl:pt-[var(--spacing-scale-024)]"
role="navigation"
aria-label={t("ariaLabels.mainNavigation")}
>
<div className="HeaderTab header-breakpoint-transition relative flex h-[var(--spacing-scale-040)] min-w-0 flex-1 items-center self-end rounded-tl-[var(--radius-measures-radius-medium)] rounded-tr-[var(--radius-measures-radius-medium)] bg-[var(--color-surface-inverse-brand-primary)] pl-[var(--spacing-scale-012)] pr-[var(--spacing-scale-012)] sm:mr-[var(--spacing-scale-008)] sm:h-[52px] sm:rounded-t-[var(--radius-measures-radius-xlarge)] sm:pr-[var(--spacing-scale-006)] md:h-[52px] md:rounded-t-[var(--radius-measures-radius-xlarge)] md:pl-[var(--spacing-scale-024)] md:pr-[var(--spacing-scale-012)] lg:h-[52px] lg:rounded-t-[var(--radius-measures-radius-xlarge)] lg:pl-[var(--spacing-scale-024)] lg:pr-[var(--spacing-scale-048)] xl:h-[64px] xl:rounded-t-[var(--radius-measures-radius-xlarge)] xl:pl-[var(--spacing-scale-032)] xl:pr-[var(--spacing-scale-120)]">
<div className="shrink-0">
<Logo
size={logoSize}
wordmark
palette={folderTop ? "inverse" : "default"}
/>
</div>
<div className="ml-auto shrink-0" data-top="nav">
<Menu
size="X Small"
className="md:gap-[var(--spacing-scale-004)] lg:gap-[var(--spacing-scale-012)]"
>
{renderNavigationItems()}
</Menu>
</div>
{/* eslint-disable-next-line @next/next/no-img-element -- decorative SVG, not content */}
<img
src={getAssetPath(ASSETS.UNION_XSM)}
alt=""
role="presentation"
className="absolute -bottom-[3px] -right-[52px] -z-10 h-[24px] w-[61px] sm:hidden sm:h-[31.5px] sm:w-[61px]"
/>
{/* eslint-disable-next-line @next/next/no-img-element -- decorative SVG */}
<img
src={getAssetPath(ASSETS.UNION_SM_MD_LG)}
alt=""
role="presentation"
className="absolute -bottom-[3.7px] -right-[53px] -z-10 hidden h-[24px] w-[61px] sm:block sm:h-[31.5px] sm:w-[61px] xl:hidden"
/>
{/* eslint-disable-next-line @next/next/no-img-element -- decorative SVG */}
<img
src={getAssetPath(ASSETS.UNION_XLG)}
alt=""
role="presentation"
className="absolute -bottom-[6px] -right-[94px] -z-10 hidden h-[53px] w-[105px] xl:block"
/>
</div>
<div
className="flex shrink-0 items-center gap-[var(--spacing-scale-004)] self-center md:gap-[var(--spacing-scale-010)]"
data-top="auth"
>
{loginControl}
{renderCreateRuleButton()}
</div>
</nav>
</header>
</>
);
}
/**
* Standard marketing / app top nav.
* Figma: "Navigation / Top" (Community-Rule-System, node 22078-808559).
* Three columns so the logo cannot paint over the nav cluster (no absolute
* overlay, no reserved 200px hit box below `lg`).
*/
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>
<header
className="relative z-50 w-full border-b border-[var(--border-color-default-tertiary)] bg-[var(--color-surface-default-primary)]"
role="banner"
aria-label={t("ariaLabels.mainNavigationHeader")}
>
<nav
className="grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-center px-[var(--spacing-scale-016)] py-[var(--spacing-scale-008)] sm:px-[var(--spacing-measures-spacing-016)] lg:px-[var(--spacing-measures-spacing-64,64px)] lg:py-[var(--spacing-scale-016)]"
role="navigation"
aria-label={t("ariaLabels.mainNavigation")}
>
<div className="min-w-0 shrink-0 lg:w-[200px] lg:max-w-[200px]" data-top="logo">
<Logo
size={logoSize}
wordmark
palette={folderTop ? "inverse" : "default"}
/>
</div>
<div
className="flex min-w-0 items-center justify-end md:justify-center"
data-top="nav"
>
<Menu
size="X Small"
className="min-w-0 lg:gap-[var(--spacing-scale-012)]"
>
{renderNavigationItems()}
</Menu>
</div>
<div
className="flex shrink-0 items-center gap-[var(--spacing-scale-004)] md:gap-[var(--spacing-measures-spacing-010)] lg:gap-[var(--spacing-measures-spacing-004)]"
data-top="auth"
>
{loginControl}
{renderCreateRuleButton()}
</div>
</nav>
</header>
</>
);
}
TopView.displayName = "TopView";
export default memo(TopView);
export { TopView };