Files
community-rule/app/components/navigation/Top/Top.view.tsx
T
adilalloandCursor c180e79c38 Give every public route its own description, and keep canonicals and structured data on the host that serves the page.
Decorative images on the marketing pages stay quiet, and every dialog close button is named Close.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 11:04:52 -07:00

172 lines
6.9 KiB
TypeScript

"use client";
import { memo } from "react";
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 phoneAuthItem = logIn ? renderLoginButton("md:hidden") : null;
const desktopAuthItem = logIn ? (
<Menu size="X Small" className="hidden md:flex lg:gap-[var(--space-300)]">
{renderLoginButton()}
</Menu>
) : null;
if (folderTop) {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>
<header
className="w-full overflow-visible bg-transparent"
role="banner"
aria-label={t("ariaLabels.homePageNavigationHeader")}
data-figma-node="5944-17779"
>
<nav
className="relative mx-auto flex h-[50px] items-end justify-between pl-[var(--space-200)] pr-[var(--space-400)] pt-[var(--space-250)] sm:h-[62px] sm:px-[var(--space-250)] sm:pr-[var(--space-500)] sm:pt-[var(--space-250)] md:h-[68px] md:px-[var(--space-400)] md:pr-[var(--space-800)] md:pt-[var(--space-400)] lg:h-[68px] lg:pl-[var(--space-600)] lg:pr-[var(--space-1400)] lg:pt-[var(--space-400)] xl:h-[88px] xl:pl-[var(--space-1200)] xl:pr-[var(--space-1400)] xl:pt-[var(--space-600)]"
role="navigation"
aria-label={t("ariaLabels.mainNavigation")}
>
<div className="HeaderTab header-breakpoint-transition relative flex h-[var(--space-1000)] min-w-0 flex-1 items-center justify-between self-end overflow-visible rounded-tl-[var(--radius-400)] rounded-tr-[var(--radius-400)] bg-[var(--color-surface-invert-brand-primary)] pl-[var(--space-300)] pr-[var(--space-300)] sm:mr-[var(--space-200)] sm:h-[52px] sm:rounded-t-[var(--radius-500)] sm:pr-[var(--space-150)] md:h-[52px] md:rounded-t-[var(--radius-500)] md:pl-[var(--space-600)] md:pr-[var(--space-300)] lg:mr-[var(--space-600)] lg:h-[52px] lg:rounded-t-[var(--radius-500)] lg:pl-[var(--space-600)] lg:pr-[var(--space-1200)] xl:mr-[var(--space-800)] xl:h-[64px] xl:rounded-t-[var(--radius-500)] xl:pl-[var(--space-800)] xl:pr-[var(--space-3000)]">
<div className="relative z-[1] shrink-0">
<Logo
size={logoSize}
wordmark
palette={folderTop ? "inverse" : "default"}
/>
</div>
<div
className="relative z-[1] min-w-0 md:absolute md:top-1/2 md:left-[calc(50vw-var(--space-400))] md:-translate-x-1/2 md:-translate-y-1/2 lg:left-[calc(50vw-var(--space-600))] xl:left-[calc(50vw-var(--space-1200))]"
data-top="nav"
>
<Menu
size="X Small"
className="md:gap-[var(--space-100)] lg:gap-[var(--space-300)]"
>
{renderNavigationItems()}
{phoneAuthItem}
</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="pointer-events-none absolute -bottom-[3px] -right-[52px] z-0 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="pointer-events-none absolute -bottom-[3.7px] -right-[53px] z-0 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="pointer-events-none absolute -bottom-[6px] -right-[94px] z-0 hidden h-[53px] w-[105px] xl:block"
/>
</div>
<div
className="flex shrink-0 items-center gap-[var(--space-100)] self-center md:gap-[var(--space-250)]"
data-top="auth"
>
{desktopAuthItem}
{renderCreateRuleButton()}
</div>
</nav>
</header>
</>
);
}
/**
* Standard marketing / app top nav.
* Figma: "Navigation / Top" (Community-Rule-System, node 5944:17779).
* Below md: logo | links (with Log in / Profile) | Create rule, so the mark
* cannot paint over the cluster. From md: 1fr | auto | 1fr so the cluster
* sits on the page midline.
*/
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")}
data-figma-node="5944-17779"
>
<nav
className="flex w-full items-center gap-[var(--space-200)] px-[var(--space-400)] py-[var(--space-200)] sm:px-[var(--space-400)] md:grid md:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] md:gap-0 lg:px-[var(--space-1800)] lg:py-[var(--space-400)]"
role="navigation"
aria-label={t("ariaLabels.mainNavigation")}
>
<div
className="min-w-0 shrink-0 justify-self-start 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 flex-1 items-center justify-end md:flex-none md:justify-center"
data-top="nav"
>
<Menu
size="X Small"
className="min-w-0 lg:gap-[var(--space-300)]"
>
{renderNavigationItems()}
{phoneAuthItem}
</Menu>
</div>
<div
className="flex shrink-0 items-center justify-self-end gap-[var(--space-100)] md:gap-[var(--space-250)] lg:gap-[var(--space-100)]"
data-top="auth"
>
{desktopAuthItem}
{renderCreateRuleButton()}
</div>
</nav>
</header>
</>
);
}
TopView.displayName = "TopView";
export default memo(TopView);
export { TopView };