Component cleanup

This commit is contained in:
adilallo
2026-04-29 07:20:16 -06:00
parent 252848eba9
commit e6127f1a3f
267 changed files with 2087 additions and 2196 deletions
@@ -2,7 +2,7 @@
import { memo } from "react";
import { usePathname } from "next/navigation";
import TopNavWithPathname from "./TopNav/TopNavWithPathname";
import TopWithPathname from "./Top/TopWithPathname";
export type ConditionalNavigationClientProps = {
initialSignedIn: boolean;
@@ -22,7 +22,7 @@ const ConditionalNavigationClient = memo(
return null;
}
return <TopNavWithPathname initialSignedIn={initialSignedIn} />;
return <TopWithPathname initialSignedIn={initialSignedIn} />;
},
);
@@ -0,0 +1,35 @@
"use client";
import { memo } from "react";
import { CreateFlowFooterView } from "./CreateFlowFooter.view";
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
/**
* Figma: "Utility / CreateFlowFooter". Sticky footer for the
* create flow with a back action, optional secondary button, and progress bar.
*/
const CreateFlowFooterContainer = memo<CreateFlowFooterProps>(
({
secondButton,
progressBar = true,
proportionBarProgress,
proportionBarVariant,
onBackClick,
className = "",
}) => {
return (
<CreateFlowFooterView
secondButton={secondButton}
progressBar={progressBar}
proportionBarProgress={proportionBarProgress}
proportionBarVariant={proportionBarVariant}
onBackClick={onBackClick}
className={className}
/>
);
},
);
CreateFlowFooterContainer.displayName = "CreateFlowFooter";
export default CreateFlowFooterContainer;
@@ -0,0 +1,39 @@
import type {
ProportionBarState,
ProportionBarVariant,
} from "../../progress/ProportionBar/ProportionBar.types";
/**
* Type definitions for CreateFlowFooter component
*
* Footer component for the create rule flow with progress bar and buttons.
*/
export interface CreateFlowFooterProps {
/**
* The second button (typically "Next" button) to display on the right side
*/
secondButton?: React.ReactNode;
/**
* Whether to show the progress bar
* @default true
*/
progressBar?: boolean;
/**
* `ProportionBar` state when the bar is shown (driven by create-flow step).
* @default "1-0"
*/
proportionBarProgress?: ProportionBarState;
/**
* `ProportionBar` layout variant (Figma create-flow footer uses `segmented`).
* @default "default"
*/
proportionBarVariant?: ProportionBarVariant;
/**
* Callback function for Back button click
*/
onBackClick?: () => void;
/**
* Additional CSS classes
*/
className?: string;
}
@@ -0,0 +1,49 @@
import ProportionBar from "../../progress/ProportionBar";
import Button from "../../buttons/Button";
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
export function CreateFlowFooterView({
secondButton,
progressBar = true,
proportionBarProgress = "1-0",
proportionBarVariant: proportionBarVariantProp,
onBackClick,
className = "",
}: CreateFlowFooterProps) {
const proportionBarVariant = proportionBarVariantProp ?? "default";
return (
<footer
className={`bg-black w-full ${className}`}
role="contentinfo"
aria-label="Create Flow Footer"
>
{/* Progress Bar - Top */}
{progressBar && (
<div className="px-[var(--spacing-measures-spacing-500,20px)] md:px-[var(--spacing-measures-spacing-1200,48px)] pt-[var(--spacing-measures-spacing-300,12px)]">
<ProportionBar
progress={proportionBarProgress}
variant={proportionBarVariant}
/>
</div>
)}
{/* Buttons Container */}
<div className="flex items-center justify-between mx-auto max-w-[639px] md:max-w-[1920px] px-[var(--spacing-measures-spacing-500,20px)] md:px-[var(--spacing-measures-spacing-1200,48px)] py-[var(--spacing-measures-spacing-300,12px)] gap-[var(--spacing-measures-spacing-300,12px)]">
{/* Back Button - Left */}
<Button
buttonType="ghost"
palette="default"
size="xsmall"
className="md:!text-[14px] md:!leading-[16px] !text-[12px] !leading-[14px] !px-[var(--spacing-measures-spacing-200,8px)] md:!px-[var(--spacing-measures-spacing-250,10px)] !py-[var(--spacing-measures-spacing-200,8px)] md:!py-[var(--spacing-measures-spacing-250,10px)]"
onClick={onBackClick}
disabled={!onBackClick}
>
Back
</Button>
{/* Second Button - Right */}
{secondButton && <div className="flex-shrink-0">{secondButton}</div>}
</div>
</footer>
);
}
@@ -0,0 +1,2 @@
export { default } from "./CreateFlowFooter.container";
export type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
@@ -0,0 +1,55 @@
"use client";
import { memo } from "react";
import { useRouter } from "next/navigation";
import { CreateFlowTopNavView } from "./CreateFlowTopNav.view";
import type { CreateFlowTopNavProps } from "./CreateFlowTopNav.types";
/**
* Figma: Utility / CreateFlowTopNav — wizard header (create-flow chrome).
* Exit, optional share / export / edit; strings in `messages/en/create/topNav.json`.
*/
const CreateFlowTopNavContainer = memo<CreateFlowTopNavProps>(
({
hasShare = false,
hasExport = false,
hasEdit = false,
saveDraftOnExit = false,
onShare,
onExport,
onEdit,
onExit,
buttonPalette,
className = "",
}) => {
const router = useRouter();
const handleExit = (options?: { saveDraft?: boolean }) => {
if (onExit) {
onExit(options);
} else {
// Default behavior: navigate to home
router.push("/");
}
};
return (
<CreateFlowTopNavView
hasShare={hasShare}
hasExport={hasExport}
hasEdit={hasEdit}
saveDraftOnExit={saveDraftOnExit}
onShare={onShare}
onExport={onExport}
onEdit={onEdit}
onExit={handleExit}
buttonPalette={buttonPalette}
className={className}
/>
);
},
);
CreateFlowTopNavContainer.displayName = "CreateFlowTopNav";
export default CreateFlowTopNavContainer;
@@ -0,0 +1,56 @@
/**
* Type definitions for CreateFlowTopNav component
*
* Top navigation bar for the create rule flow.
* Includes logo and action buttons (Share, Export, Edit, Exit).
*/
export interface CreateFlowTopNavProps {
/**
* Whether to show the Share button
* @default false
*/
hasShare?: boolean;
/**
* Whether to show the Export button
* @default false
*/
hasExport?: boolean;
/**
* Whether to show the Edit button
* @default false
*/
hasEdit?: boolean;
/**
* When true, exit control is "Save & Exit" and `onExit` receives `{ saveDraft: true }`.
* When false, shows "Exit" and `{ saveDraft: false }` (caller may confirm data loss).
* @default false
*/
saveDraftOnExit?: boolean;
/**
* Callback when Share button is clicked
*/
onShare?: () => void;
/**
* Callback when Export button is clicked
*/
onExport?: () => void;
/**
* Callback when Edit button is clicked
*/
onEdit?: () => void;
/**
* Callback when Exit/Save & Exit button is clicked.
* When `saveDraftOnExit` is true, called with `{ saveDraft: true }`.
*/
onExit?: (options?: { saveDraft?: boolean }) => void;
/**
* Palette for nav buttons (e.g. "inverse" on completed page to match teal background)
* @default "default"
*/
buttonPalette?: "default" | "inverse";
/**
* Additional CSS classes
*/
className?: string;
}
@@ -0,0 +1,108 @@
"use client";
import Logo from "../../asset/Logo";
import Button from "../../buttons/Button";
import { useTranslation } from "../../../contexts/MessagesContext";
import type { CreateFlowTopNavProps } from "./CreateFlowTopNav.types";
const exitButtonFigmaClass =
"!rounded-[var(--radius-measures-radius-full,9999px)] !border-[1.25px] !px-[var(--spacing-measures-spacing-250,10px)] !py-[var(--spacing-measures-spacing-200,8px)] md:!text-[12px] md:!leading-[14px]";
export function CreateFlowTopNavView({
hasShare = false,
hasExport = false,
hasEdit = false,
saveDraftOnExit = false,
onShare,
onExport,
onEdit,
onExit,
buttonPalette = "default",
className = "",
}: CreateFlowTopNavProps) {
const t = useTranslation("create.topNav");
const exitButtonText = saveDraftOnExit ? t("saveAndExit") : t("exit");
return (
<header
className={`bg-black w-full ${className}`}
role="banner"
aria-label={t("bannerAriaLabel")}
>
<nav
className="flex items-center justify-between mx-auto max-w-[639px] md:max-w-[1920px] px-[var(--spacing-measures-spacing-500,20px)] md:px-[48px] py-[var(--spacing-measures-spacing-300,12px)] md:py-[var(--spacing-measures-spacing-016,16px)]"
role="navigation"
aria-label={t("navAriaLabel")}
>
<Logo size="createFlow" wordmark palette={buttonPalette} />
<div className="flex flex-wrap items-center justify-end gap-[var(--spacing-scale-012,12px)]">
{hasShare && (
<Button
buttonType="outline"
palette={buttonPalette}
size="xsmall"
onClick={onShare}
ariaLabel={t("shareAriaLabel")}
className="md:!text-[12px] md:!leading-[14px] !text-[10px] !leading-[12px] !px-[var(--spacing-scale-006,6px)] md:!px-[var(--spacing-scale-008,8px)] !py-[6px] md:!py-[8px] !border md:!border-[1.5px]"
>
{t("share")}
</Button>
)}
{hasExport && (
<Button
buttonType="outline"
palette={buttonPalette}
size="xsmall"
onClick={onExport}
ariaLabel={t("exportAriaLabel")}
className="justify-center gap-[var(--spacing-scale-002,2px)] !pl-[var(--spacing-scale-012,12px)] !pr-[var(--spacing-scale-006,6px)] md:!pr-[var(--spacing-scale-006,6px)] !text-[10px] md:!text-[12px] !leading-[12px] md:!leading-[14px] !py-[6px] md:!py-[8px] !border md:!border-[1.5px]"
>
<span>{t("export")}</span>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="shrink-0 md:w-[14px] md:h-[14px]"
aria-hidden="true"
>
<path d="M19 9l-7 7-7-7" />
</svg>
</Button>
)}
{hasEdit && (
<Button
buttonType="outline"
palette={buttonPalette}
size="xsmall"
onClick={onEdit}
ariaLabel={t("editAriaLabel")}
className="md:!text-[12px] md:!leading-[14px] !text-[10px] !leading-[12px] !px-[var(--spacing-scale-006,6px)] md:!px-[var(--spacing-scale-008,8px)] !py-[6px] md:!py-[8px] !border md:!border-[1.5px]"
>
{t("edit")}
</Button>
)}
<Button
buttonType="outline"
palette={buttonPalette}
size="xsmall"
type="button"
onClick={() => void onExit?.({ saveDraft: saveDraftOnExit })}
ariaLabel={exitButtonText}
className={`md:!text-[12px] md:!leading-[14px] !text-[10px] !leading-[12px] !py-[6px] md:!py-[8px] shrink-0 ${exitButtonFigmaClass}`}
>
{exitButtonText}
</Button>
</div>
</nav>
</header>
);
}
@@ -0,0 +1,2 @@
export { default } from "./CreateFlowTopNav.container";
export type { CreateFlowTopNavProps } from "./CreateFlowTopNav.types";
+3 -3
View File
@@ -3,8 +3,8 @@
import { memo } from "react";
import { useTranslation } from "../../contexts/MessagesContext";
import Link from "next/link";
import Logo from "../asset/logo";
import Separator from "../utility/Separator";
import Logo from "../asset/Logo";
import Divider from "../utility/Divider";
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
/**
@@ -158,7 +158,7 @@ const Footer = memo(() => {
</div>
</div>
<Separator />
<Divider />
<div
className="flex w-full flex-col
@@ -3,27 +3,27 @@
import { memo } from "react";
import { useTranslation } from "../../contexts/MessagesContext";
export type MenuBarSizeValue =
export type MenuSizeValue =
| "X Small"
| "Small"
| "Medium"
| "Large"
| "X Large";
interface MenuBarProps extends React.HTMLAttributes<HTMLElement> {
interface MenuProps extends React.HTMLAttributes<HTMLElement> {
children?: React.ReactNode;
className?: string;
/**
* Menu bar size. Uses Figma format: "X Small", "Small", "Medium", "Large", "X Large".
* Menu size. Uses Figma format: "X Small", "Small", "Medium", "Large", "X Large".
* @default "X Small"
*/
size?: MenuBarSizeValue;
size?: MenuSizeValue;
}
const MenuBar = memo<MenuBarProps>(
const Menu = memo<MenuProps>(
({ children, className = "", size: sizeProp = "X Small", ...props }) => {
const size = sizeProp ?? "X Small";
const t = useTranslation("menuBar");
const t = useTranslation("menu");
// Size styles based on Figma specifications
const sizeStyles: Record<
@@ -57,6 +57,6 @@ const MenuBar = memo<MenuBarProps>(
},
);
MenuBar.displayName = "MenuBar";
Menu.displayName = "Menu";
export default MenuBar;
export default Menu;
@@ -1,2 +0,0 @@
export { default } from "./MenuBarItem.container";
export type { MenuBarItemProps } from "./MenuBarItem.types";
@@ -1,10 +1,10 @@
"use client";
import { memo } from "react";
import MenuBarItemView from "./MenuBarItem.view";
import type { MenuBarItemProps } from "./MenuBarItem.types";
import MenuItemView from "./MenuItem.view";
import type { MenuItemProps } from "./MenuItem.types";
const MenuBarItemContainer = memo<MenuBarItemProps>(
const MenuItemContainer = memo<MenuItemProps>(
({
href = "#",
buttonOnClick,
@@ -106,7 +106,7 @@ const MenuBarItemContainer = memo<MenuBarItemProps>(
};
return (
<MenuBarItemView
<MenuItemView
href={href}
buttonOnClick={buttonOnClick}
disabled={disabled}
@@ -115,11 +115,11 @@ const MenuBarItemContainer = memo<MenuBarItemProps>(
accessibilityProps={accessibilityProps}
>
{children}
</MenuBarItemView>
</MenuItemView>
);
},
);
MenuBarItemContainer.displayName = "MenuBarItem";
MenuItemContainer.displayName = "MenuItem";
export default MenuBarItemContainer;
export default MenuItemContainer;
@@ -1,40 +1,40 @@
export type MenuBarItemSizeValue =
export type MenuItemSizeValue =
| "X Small"
| "Small"
| "Medium"
| "Large"
| "X Large";
export type MenuBarItemStateValue = "default" | "hover" | "selected";
export type MenuItemStateValue = "default" | "hover" | "selected";
export type MenuBarItemModeValue = "default" | "inverse";
export type MenuItemModeValue = "default" | "inverse";
export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
export interface MenuItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
href?: string;
/** When set, renders a `<button type="button">` instead of a link (e.g. open login modal). */
buttonOnClick?: () => void;
children?: React.ReactNode;
/**
* Menu bar item state: "default", "hover", or "selected".
* Menu item state: "default", "hover", or "selected".
* @default "default"
*/
state?: MenuBarItemStateValue;
state?: MenuItemStateValue;
/**
* Menu bar item mode. Default mode has yellow text on dark background.
* Menu item mode. Default mode has yellow text on dark background.
* Inverse mode has black text on yellow background (for folderTop variant).
* @default "default"
*/
mode?: MenuBarItemModeValue;
mode?: MenuItemModeValue;
/**
* Whether to show an icon (for future icon support).
* @default false
*/
icon?: boolean;
/**
* Menu bar item size. Uses Figma format: "X Small", "Small", "Medium", "Large", "X Large".
* Menu item size. Uses Figma format: "X Small", "Small", "Medium", "Large", "X Large".
* @default "X Small"
*/
size?: MenuBarItemSizeValue;
size?: MenuItemSizeValue;
className?: string;
disabled?: boolean;
/**
@@ -45,7 +45,7 @@ export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorE
ariaLabel?: string;
}
export interface MenuBarItemViewProps {
export interface MenuItemViewProps {
href: string;
buttonOnClick?: () => void;
children?: React.ReactNode;
@@ -1,14 +1,14 @@
import { memo } from "react";
import type { MenuBarItemViewProps } from "./MenuBarItem.types";
import type { MenuItemViewProps } from "./MenuItem.types";
function MenuBarItemView({
function MenuItemView({
href,
buttonOnClick,
children,
disabled,
combinedStyles,
accessibilityProps,
}: MenuBarItemViewProps) {
}: MenuItemViewProps) {
if (disabled) {
return (
<span className={combinedStyles} {...accessibilityProps}>
@@ -37,6 +37,6 @@ function MenuBarItemView({
);
}
MenuBarItemView.displayName = "MenuBarItemView";
MenuItemView.displayName = "MenuItemView";
export default memo(MenuBarItemView);
export default memo(MenuItemView);
@@ -0,0 +1,2 @@
export { default } from "./MenuItem.container";
export type { MenuItemProps } from "./MenuItem.types";
@@ -4,15 +4,31 @@ import { memo, useCallback } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useAuthModal } from "../../../contexts/AuthModalContext";
import { useTranslation } from "../../../contexts/MessagesContext";
import MenuBarItem from "../MenuBarItem";
import MenuItem from "../MenuItem";
import Button from "../../buttons/Button";
import AvatarContainer from "../../utility/AvatarContainer";
import Avatar from "../../icons/Avatar";
import AvatarContainer from "../../asset/AvatarContainer";
import Avatar from "../../asset/Avatar";
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
import { clearAnonymousCreateFlowStorage } from "../../../(app)/create/utils/anonymousDraftStorage";
import { clearCoreValueDetailsLocalStorage } from "../../../(app)/create/utils/coreValueDetailsLocalStorage";
import { TopNavView } from "./TopNav.view";
import type { TopNavProps, NavSize } from "./TopNav.types";
import { TopView } from "./Top.view";
import type { TopProps, NavSize } from "./Top.types";
type MenuClusterSize = "X Small" | "Small" | "Medium" | "Large" | "X Large";
/** Map responsive `NavSize` breakpoints to Figma menu item sizes (shared by nav links + login). */
const NAV_SIZE_TO_MENU_ITEM_SIZE: Record<NavSize, MenuClusterSize> = {
default: "Small",
xsmall: "X Small",
xsmallUseCases: "X Small",
home: "X Small",
homeMd: "Medium",
homeUseCases: "Small",
large: "Large",
largeUseCases: "Large",
homeXlarge: "X Large",
xlarge: "X Large",
};
export const avatarImages = [
{ src: getAssetPath(ASSETS.AVATAR_1), alt: "Avatar 1" },
@@ -20,7 +36,7 @@ export const avatarImages = [
{ src: getAssetPath(ASSETS.AVATAR_3), alt: "Avatar 3" },
];
const TopNavContainer = memo<TopNavProps>(
const TopContainer = memo<TopProps>(
({ folderTop = false, loggedIn = false, profile = false, logIn = true }) => {
const pathname = usePathname();
const router = useRouter();
@@ -28,7 +44,7 @@ const TopNavContainer = memo<TopNavProps>(
const t = useTranslation("header");
/**
* TopNav is hidden on `/create` routes by ConditionalNavigationClient, so
* `Top` is hidden on `/create` routes by ConditionalNavigationClient, so
* this button is always clicked from outside the wizard there is no
* mounted CreateFlowProvider to reset. Wiping the anonymous draft keys
* here guarantees a fresh start; the provider that mounts on `/create`
@@ -68,35 +84,15 @@ const TopNavContainer = memo<TopNavProps>(
];
const renderNavigationItems = (size: NavSize) => {
// Map NavSize to Figma MenuBarItem sizes
const sizeMap: Record<
NavSize,
"X Small" | "Small" | "Medium" | "Large" | "X Large"
> = {
default: "Small",
xsmall: "X Small",
xsmallUseCases: "X Small",
home: "X Small",
homeMd: "Medium",
homeUseCases: "Small",
large: "Large",
largeUseCases: "Large",
homeXlarge: "X Large",
xlarge: "X Large",
};
// Determine mode based on folderTop
const mode = folderTop ? "inverse" : "default";
return navigationItems.map((item, index) => {
// Map size to Figma size
let itemSize = sizeMap[size] || "Small";
const itemSize = NAV_SIZE_TO_MENU_ITEM_SIZE[size];
// Pass reducedPadding for "use cases" button (item with extraPadding: true)
const isUseCases = item.extraPadding === true;
return (
<MenuBarItem
<MenuItem
key={index}
href={item.href}
size={itemSize}
@@ -109,7 +105,7 @@ const TopNavContainer = memo<TopNavProps>(
)}
>
{item.text}
</MenuBarItem>
</MenuItem>
);
});
};
@@ -133,22 +129,7 @@ const TopNavContainer = memo<TopNavProps>(
};
const renderLoginButton = (size: NavSize) => {
// Map NavSize to Figma MenuBarItem sizes
const sizeMap: Record<
NavSize,
"X Small" | "Small" | "Medium" | "Large" | "X Large"
> = {
default: "Small",
xsmall: "X Small",
xsmallUseCases: "X Small",
home: "X Small",
homeMd: "Medium",
homeUseCases: "Small",
large: "Large",
largeUseCases: "Large",
homeXlarge: "X Large",
xlarge: "X Large",
};
const itemSize = NAV_SIZE_TO_MENU_ITEM_SIZE[size];
// Determine mode based on folderTop and breakpoint size
// folderTop: inverse mode (black text) for smallest breakpoints (xsmall/home)
@@ -167,20 +148,20 @@ const TopNavContainer = memo<TopNavProps>(
if (loggedIn) {
return (
<MenuBarItem
<MenuItem
href="/profile"
size={sizeMap[size] || "Small"}
size={itemSize}
mode={mode}
state={navSelected ? "selected" : "default"}
ariaLabel={ariaLabel}
>
{label}
</MenuBarItem>
</MenuItem>
);
}
return (
<MenuBarItem
<MenuItem
buttonOnClick={() =>
openLogin({
variant: "default",
@@ -189,13 +170,13 @@ const TopNavContainer = memo<TopNavProps>(
})
}
href="/login"
size={sizeMap[size] || "Small"}
size={itemSize}
mode={mode}
state={navSelected ? "selected" : "default"}
ariaLabel={ariaLabel}
>
{label}
</MenuBarItem>
</MenuItem>
);
};
@@ -223,7 +204,7 @@ const TopNavContainer = memo<TopNavProps>(
};
return (
<TopNavView
<TopView
folderTop={folderTop}
loggedIn={loggedIn}
profile={profile}
@@ -238,6 +219,6 @@ const TopNavContainer = memo<TopNavProps>(
},
);
TopNavContainer.displayName = "TopNav";
TopContainer.displayName = "Top";
export default TopNavContainer;
export default TopContainer;
@@ -1,6 +1,6 @@
import type React from "react";
export interface TopNavProps {
export interface TopProps {
className?: string;
size?: "320-429" | "430-639" | "640-1023" | "1024-1440" | "1440+";
loggedIn?: boolean;
@@ -21,7 +21,7 @@ export type NavSize =
| "homeXlarge"
| "xlarge";
export interface TopNavViewProps {
export interface TopViewProps {
folderTop: boolean;
loggedIn: boolean;
profile: boolean;
@@ -4,12 +4,12 @@ import { memo } from "react";
import Script from "next/script";
import { useTranslation } from "../../../contexts/MessagesContext";
import { getAssetPath } from "../../../../lib/assetUtils";
import MenuBar from "../MenuBar";
import type { TopNavViewProps } from "./TopNav.types";
import Menu from "../Menu";
import type { TopViewProps } from "./Top.types";
import Logo from "../../asset/logo";
import Logo from "../../asset/Logo";
function TopNavView({
function TopView({
folderTop,
loggedIn: _loggedIn,
profile: _profile,
@@ -19,7 +19,7 @@ function TopNavView({
renderNavigationItems,
renderLoginButton,
renderCreateRuleButton,
}: TopNavViewProps) {
}: TopViewProps) {
const t = useTranslation(folderTop ? "homeHeader" : "header");
// Render folderTop variant (HomeHeader style)
@@ -27,7 +27,7 @@ function TopNavView({
return (
<>
<Script
id="top-nav-schema"
id="top-navigation-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>
@@ -50,12 +50,12 @@ function TopNavView({
palette={folderTop ? "inverse" : "default"}
/>
{/* XSmall menu bar - positioned next to logo */}
{/* XSmall menu positioned next to logo */}
<div className="block sm:hidden -me-[2px]">
<MenuBar size="X Small">
<Menu size="X Small">
{renderNavigationItems("xsmall")}
{logIn && renderLoginButton("xsmall")}
</MenuBar>
</Menu>
</div>
{/* Decorative Union images for tab appearance */}
@@ -84,31 +84,31 @@ function TopNavView({
{/* Navigation Links - Centered in header for SM and up */}
<div className="absolute left-1/2 transform -translate-x-1/2 hidden sm:block">
{/* 430-639px (sm: breakpoint): MenuBar X Small */}
{/* 430-639px (sm: breakpoint): Menu X Small */}
<div className="hidden sm:block md:hidden">
<MenuBar size="X Small">
<Menu size="X Small">
{renderNavigationItems("xsmall")}
{logIn && renderLoginButton("xsmall")}
</MenuBar>
</Menu>
</div>
{/* 640-1023px (md: breakpoint): MenuBar Small */}
{/* 640-1023px (md: breakpoint): Menu Small */}
<div className="hidden md:block lg:hidden">
<MenuBar size="Small">
<Menu size="Small">
{renderNavigationItems("homeMd")}
</MenuBar>
</Menu>
</div>
{/* 1024-1440px (lg: breakpoint): MenuBar Large */}
{/* 1024-1440px (lg: breakpoint): Menu Large */}
<div className="hidden lg:block xl:hidden">
<MenuBar size="Large">{renderNavigationItems("large")}</MenuBar>
<Menu size="Large">{renderNavigationItems("large")}</Menu>
</div>
{/* 1440px+ (xl: breakpoint): MenuBar X Large */}
{/* 1440px+ (xl: breakpoint): Menu X Large */}
<div className="hidden xl:block">
<MenuBar size="X Large">
<Menu size="X Large">
{renderNavigationItems("homeXlarge")}
</MenuBar>
</Menu>
</div>
</div>
@@ -153,7 +153,7 @@ function TopNavView({
* Standard marketing / app top nav.
* Figma: "Navigation / Top" (Community-Rule-System, node 22078-808559) horizontal
* padding, logo ~200px left, menu cluster centered in the bar (`left-1/2` + translate),
* log in + create rule on the right. Breakpoints and MenuBar sizes unchanged from prior map.
* log in + create rule on the right. Breakpoints and Menu sizes unchanged from prior map.
*/
// Render standard variant (Header style)
return (
@@ -182,7 +182,7 @@ function TopNavView({
>
<div
className="relative z-20 min-w-0 shrink-0 sm:w-[200px] sm:max-w-[200px] sm:shrink-0"
data-topnav="logo"
data-top="logo"
>
<Logo
size={logoSize}
@@ -194,51 +194,51 @@ function TopNavView({
{/* XSmall: nav + login in flow (flex-1) — same as before */}
<div
className="flex min-w-0 flex-1 items-center justify-end sm:hidden"
data-topnav="nav-xs-flow"
data-top="nav-xs-flow"
>
<div className="block" data-testid="nav-xs">
<MenuBar size="X Small">
<Menu size="X Small">
{renderNavigationItems("xsmall")}
{logIn && renderLoginButton("xsmall")}
</MenuBar>
</Menu>
</div>
</div>
{/* sm+ — Figma: nav cluster centered in bar (not between logo and actions) */}
<div
className="pointer-events-none hidden sm:absolute sm:left-1/2 sm:top-1/2 sm:z-10 sm:flex sm:-translate-x-1/2 sm:-translate-y-1/2 sm:items-center sm:justify-center"
data-topnav="nav-center"
data-top="nav-center"
>
<div
className="pointer-events-auto hidden sm:flex md:hidden"
data-testid="nav-sm"
>
<MenuBar size="X Small">
<Menu size="X Small">
{renderNavigationItems("xsmall")}
{logIn && renderLoginButton("xsmall")}
</MenuBar>
</Menu>
</div>
<div
className="pointer-events-auto hidden md:flex lg:hidden"
data-testid="nav-md"
>
<MenuBar size="X Small">
<Menu size="X Small">
{renderNavigationItems("xsmall")}
</MenuBar>
</Menu>
</div>
<div
className="pointer-events-auto hidden lg:flex xl:hidden"
data-testid="nav-lg"
>
<MenuBar size="Large">{renderNavigationItems("large")}</MenuBar>
<Menu size="Large">{renderNavigationItems("large")}</Menu>
</div>
<div
className="pointer-events-auto hidden xl:flex"
data-testid="nav-xl"
>
<MenuBar size="X Large">
<Menu size="X Large">
{renderNavigationItems("xlarge")}
</MenuBar>
</Menu>
</div>
</div>
@@ -259,9 +259,9 @@ function TopNavView({
{/* Medium breakpoint */}
<div className="hidden md:block lg:hidden" data-testid="auth-md">
<div className="flex items-center gap-[var(--spacing-measures-spacing-010)]">
<MenuBar size="Small">
<Menu size="Small">
{logIn && renderLoginButton("xsmall")}
</MenuBar>
</Menu>
{renderCreateRuleButton("xsmall", "medium", "medium")}
</div>
</div>
@@ -269,9 +269,9 @@ function TopNavView({
{/* Large breakpoint */}
<div className="hidden lg:block xl:hidden" data-testid="auth-lg">
<div className="flex items-center gap-[var(--spacing-measures-spacing-004)]">
<MenuBar size="Large">
<Menu size="Large">
{logIn && renderLoginButton("large")}
</MenuBar>
</Menu>
{renderCreateRuleButton("large", "xlarge", "xlarge")}
</div>
</div>
@@ -279,9 +279,9 @@ function TopNavView({
{/* XLarge breakpoint */}
<div className="hidden xl:block" data-testid="auth-xl">
<div className="flex items-center gap-[var(--spacing-measures-spacing-004)]">
<MenuBar size="X Large">
<Menu size="X Large">
{logIn && renderLoginButton("xlarge")}
</MenuBar>
</Menu>
{renderCreateRuleButton("xlarge", "xlarge", "xlarge")}
</div>
</div>
@@ -292,7 +292,7 @@ function TopNavView({
);
}
TopNavView.displayName = "TopNavView";
TopView.displayName = "TopView";
export default memo(TopNavView);
export { TopNavView };
export default memo(TopView);
export { TopView };
@@ -2,17 +2,17 @@
import { memo, useCallback, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import TopNav from "./TopNav.container";
import type { TopNavProps } from "./TopNav.types";
import Top from "./Top.container";
import type { TopProps } from "./Top.types";
import { fetchAuthSession } from "../../../../lib/create/api";
export type TopNavWithPathnameProps = Omit<TopNavProps, "folderTop"> & {
export type TopWithPathnameProps = Omit<TopProps, "folderTop"> & {
/** From Server Component (`getNavAuthSignedIn`); matches first HTML paint. */
initialSignedIn?: boolean;
};
/**
* TopNav wrapper: `folderTop` from pathname; Log in vs Profile from session.
* `Top` wrapper: `folderTop` from pathname; Log in vs Profile from session.
*
* **SSR:** Parent passes `initialSignedIn` from `getSessionUser()` so the hydrated
* header matches the cookie (Next.js pattern for HttpOnly session UI).
@@ -20,8 +20,8 @@ export type TopNavWithPathnameProps = Omit<TopNavProps, "folderTop"> & {
* **Client:** Refetch on pathname change (magic-link redirect, stale layout after
* `router.refresh()`), **popstate** / **pageshow** `persisted` (bfcache / back).
*/
const TopNavWithPathname = memo<TopNavWithPathnameProps>((props) => {
const { initialSignedIn = false, ...topNavRest } = props;
const TopWithPathname = memo<TopWithPathnameProps>((props) => {
const { initialSignedIn = false, ...topRest } = props;
const pathname = usePathname();
const isHomePage = pathname === "/";
const [loggedIn, setLoggedIn] = useState(initialSignedIn);
@@ -69,9 +69,9 @@ const TopNavWithPathname = memo<TopNavWithPathnameProps>((props) => {
return () => window.removeEventListener("popstate", onPopState);
}, [syncSession]);
return <TopNav {...topNavRest} folderTop={isHomePage} loggedIn={loggedIn} />;
return <Top {...topRest} folderTop={isHomePage} loggedIn={loggedIn} />;
});
TopNavWithPathname.displayName = "TopNavWithPathname";
TopWithPathname.displayName = "TopWithPathname";
export default TopNavWithPathname;
export default TopWithPathname;
+3
View File
@@ -0,0 +1,3 @@
export { default } from "./Top.container";
export type { TopProps, NavSize } from "./Top.types";
export { avatarImages } from "./Top.container";
@@ -1,3 +0,0 @@
export { default } from "./TopNav.container";
export type { TopNavProps, NavSize } from "./TopNav.types";
export { avatarImages } from "./TopNav.container";