Update TopNav component
This commit is contained in:
@@ -3,152 +3,108 @@
|
||||
import { memo } from "react";
|
||||
import MenuBarItemView from "./MenuBarItem.view";
|
||||
import type { MenuBarItemProps } from "./MenuBarItem.types";
|
||||
import { normalizeMenuBarItemVariant } from "../../../../lib/propNormalization";
|
||||
import {
|
||||
normalizeMenuBarItemState,
|
||||
normalizeMenuBarItemMode,
|
||||
normalizeMenuBarItemSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const MenuBarItemContainer = memo<MenuBarItemProps>(
|
||||
({
|
||||
href = "#",
|
||||
children,
|
||||
variant: variantProp = "default",
|
||||
size: sizeProp = "default",
|
||||
state: stateProp,
|
||||
mode: modeProp,
|
||||
icon: _icon = false,
|
||||
size: sizeProp = "X Small",
|
||||
className = "",
|
||||
disabled = false,
|
||||
isActive = false,
|
||||
reducedPadding = false,
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeMenuBarItemVariant(variantProp);
|
||||
// Size has many values, normalize by lowercasing
|
||||
const size = (sizeProp?.toLowerCase() || "default") as typeof sizeProp;
|
||||
const variantStyles: Record<string, string> = {
|
||||
const state = normalizeMenuBarItemState(stateProp, "default");
|
||||
const mode = normalizeMenuBarItemMode(modeProp, "default");
|
||||
const size = normalizeMenuBarItemSize(sizeProp, "X Small");
|
||||
|
||||
// Size styles based on Figma specifications
|
||||
const sizeStyles: Record<
|
||||
"X Small" | "Small" | "Medium" | "Large" | "X Large",
|
||||
string
|
||||
> = {
|
||||
"X Small": reducedPadding ? "px-[var(--spacing-scale-004)] py-[var(--spacing-scale-002)]" : "px-[var(--spacing-scale-004)] py-[var(--spacing-scale-002)]",
|
||||
Small: "px-[var(--spacing-scale-004)] py-[var(--spacing-scale-002)]",
|
||||
Medium: reducedPadding ? "px-[var(--spacing-scale-002)] py-[var(--spacing-scale-008)] h-[32px]" : "px-[var(--spacing-scale-008)] py-[var(--spacing-scale-008)] h-[32px]",
|
||||
Large: "px-[var(--spacing-scale-016)] py-[var(--spacing-scale-016)] h-[44px]",
|
||||
"X Large": "px-[var(--spacing-scale-016)] py-[var(--spacing-scale-016)] h-[44px]",
|
||||
};
|
||||
|
||||
// Text styles based on Figma specifications
|
||||
const textStyles: Record<
|
||||
"X Small" | "Small" | "Medium" | "Large" | "X Large",
|
||||
string
|
||||
> = {
|
||||
"X Small":
|
||||
"font-inter text-[10px] leading-[12px] font-medium tracking-[0%]",
|
||||
Small:
|
||||
"font-inter text-[12px] leading-[14px] font-medium tracking-[0%]",
|
||||
Medium:
|
||||
"font-inter text-[12px] leading-[14px] font-medium tracking-[0%]",
|
||||
Large:
|
||||
"font-inter text-[16px] leading-[20px] font-medium tracking-[0%]",
|
||||
"X Large":
|
||||
"font-inter text-[24px] leading-[28px] font-normal tracking-[0%]",
|
||||
};
|
||||
|
||||
// State styles for Default mode (yellow text on dark background)
|
||||
const defaultModeStyles: Record<
|
||||
"default" | "hover" | "selected",
|
||||
string
|
||||
> = {
|
||||
default:
|
||||
"bg-transparent text-[var(--color-content-default-brand-primary)] hover:bg-[var(--color-surface-default-tertiary)] hover:text-[var(--color-content-default-brand-primary)] hover:scale-[1.02] active:bg-transparent active:text-[var(--color-content-default-brand-primary)] active:scale-[0.98] disabled:bg-[var(--color-surface-default-tertiary)] disabled:text-[var(--color-content-default-tertiary)] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100",
|
||||
home: "bg-transparent text-[var(--color-content-inverse-primary)] hover:bg-[var(--color-content-default-brand-accent)] hover:text-[var(--color-content-inverse-primary)] hover:scale-[1.02] active:bg-transparent active:text-[var(--color-content-inverse-primary)] active:scale-[0.98] disabled:bg-[var(--color-surface-default-tertiary)] disabled:text-[var(--color-content-default-tertiary)] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100",
|
||||
"bg-transparent text-[var(--color-content-default-brand-primary,#fefcc9)] hover:bg-[var(--color-gray-800)] hover:text-[var(--color-content-default-brand-primary,#fefcc9)]",
|
||||
hover:
|
||||
"bg-[var(--color-gray-800)] text-[var(--color-content-default-brand-primary,#fefcc9)]",
|
||||
selected:
|
||||
"border border-[var(--color-border-default-brand-primary,#fdfaa8)] text-[var(--color-content-default-brand-primary,#fefcc9)] bg-transparent hover:bg-[var(--color-gray-800)]",
|
||||
};
|
||||
|
||||
const activeOutlineStyles: Record<string, string> = {
|
||||
xsmall:
|
||||
"active:outline-1 active:outline-[var(--color-content-default-primary)] focus:outline-1 focus:outline-[var(--color-content-default-primary)]",
|
||||
xsmallUseCases:
|
||||
"active:outline-1 active:outline-[var(--color-content-default-primary)] focus:outline-1 focus:outline-[var(--color-content-default-primary)]",
|
||||
// State styles for Inverse mode (black text on yellow background)
|
||||
const inverseModeStyles: Record<
|
||||
"default" | "hover" | "selected",
|
||||
string
|
||||
> = {
|
||||
default:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
homeMd:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
homeUseCases:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
large:
|
||||
"active:outline-[1.75px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[1.75px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
largeUseCases:
|
||||
"active:outline-[1.75px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[1.75px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
homeXlarge:
|
||||
"active:outline-[2px] active:outline-[var(--color-content-default-brand-primary)] focus:outline-[2px] focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
xlarge:
|
||||
"active:outline-2 active:outline-[var(--color-content-default-brand-primary)] focus:outline-2 focus:outline-[var(--color-content-default-brand-primary)]",
|
||||
"bg-transparent text-[var(--color-content-inverse-primary,black)] hover:bg-[var(--color-surface-brand-accent,#4d4a00)] hover:text-[var(--color-content-inverse-primary,black)]",
|
||||
hover:
|
||||
"bg-[var(--color-surface-brand-accent,#4d4a00)] text-[var(--color-content-inverse-primary,black)]",
|
||||
selected:
|
||||
"border border-[var(--color-border-default-primary,#141414)] text-[var(--color-content-inverse-primary,black)] bg-transparent hover:bg-[var(--color-surface-brand-accent,#4d4a00)]",
|
||||
};
|
||||
|
||||
const homeOutlineStyles: Record<string, string> = {
|
||||
xsmall:
|
||||
"active:outline-1 active:outline-[var(--color-content-default-primary)] focus:outline-1 focus:outline-[var(--color-content-default-primary)]",
|
||||
xsmallUseCases:
|
||||
"active:outline-1 active:outline-[var(--color-content-default-primary)] focus:outline-1 focus:outline-[var(--color-content-default-primary)]",
|
||||
default:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-primary)]",
|
||||
homeMd:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-primary)]",
|
||||
homeUseCases:
|
||||
"active:outline-[1.5px] active:outline-[var(--color-content-default-primary)] focus:outline-[1.5px] focus:outline-[var(--color-content-default-primary)]",
|
||||
largeUseCases:
|
||||
"active:outline-[1.75px] active:outline-[var(--color-content-default-primary)] focus:outline-[1.75px] focus:outline-[var(--color-content-default-primary)]",
|
||||
large:
|
||||
"active:outline-[1.75px] active:outline-[var(--color-content-default-primary)] focus:outline-[1.75px] focus:outline-[var(--color-content-default-primary)]",
|
||||
homeXlarge:
|
||||
"active:outline-[2px] active:outline-[var(--color-content-default-primary)] focus:outline-[2px] focus:outline-[var(--color-content-default-primary)]",
|
||||
xlarge:
|
||||
"active:outline-2 active:outline-[var(--color-content-default-primary)] focus:outline-2 focus:outline-[var(--color-content-default-primary)]",
|
||||
};
|
||||
// Get state styles based on mode
|
||||
const stateStyles =
|
||||
mode === "inverse" ? inverseModeStyles : defaultModeStyles;
|
||||
|
||||
const activeStateStyles: Record<string, string> = {
|
||||
xsmall:
|
||||
"!outline-1 !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-1 focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
xsmallUseCases:
|
||||
"!outline-1 !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-1 focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
default:
|
||||
"!outline-[1.5px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[1.5px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
homeMd:
|
||||
"!outline-[1.5px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[1.5px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
homeUseCases:
|
||||
"!outline-[1.5px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[1.5px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
large:
|
||||
"!outline-[1.75px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[1.75px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
largeUseCases:
|
||||
"!outline-[1.75px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[1.75px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
homeXlarge:
|
||||
"!outline-[2px] !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-[2px] focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
xlarge:
|
||||
"!outline-2 !outline-[var(--color-content-default-brand-primary)] !text-[var(--color-content-default-brand-primary)] focus:!outline-2 focus:!outline-[var(--color-content-default-brand-primary)]",
|
||||
};
|
||||
// Base styles
|
||||
const baseStyles = `inline-flex items-center whitespace-nowrap ${sizeStyles[size]} ${textStyles[size]} rounded-[var(--radius-measures-radius-full)] transition-all duration-200 ease-in-out cursor-pointer`;
|
||||
|
||||
const sizeStyles: Record<string, string> = {
|
||||
default:
|
||||
"px-[var(--spacing-measures-spacing-016)] py-[var(--spacing-measures-spacing-016)] gap-[var(--spacing-scale-004)]",
|
||||
xsmall:
|
||||
"px-[var(--spacing-scale-004)] py-[var(--spacing-scale-002)] gap-[var(--spacing-scale-004)]",
|
||||
xsmallUseCases:
|
||||
"px-[var(--spacing-scale-002)] py-[var(--spacing-scale-002)] gap-[var(--spacing-scale-004)]",
|
||||
homeMd:
|
||||
"px-[var(--spacing-scale-008)] py-[var(--spacing-scale-008)] gap-[var(--spacing-scale-004)]",
|
||||
homeUseCases:
|
||||
"px-[var(--spacing-scale-002)] py-[var(--spacing-scale-008)] gap-[var(--spacing-scale-004)]",
|
||||
large:
|
||||
"px-[var(--spacing-scale-012)] py-[var(--spacing-scale-012)] gap-[var(--spacing-scale-004)] h-[44px]",
|
||||
largeUseCases:
|
||||
"px-[var(--spacing-scale-012)] py-[var(--spacing-scale-012)] gap-[var(--spacing-scale-004)] h-[44px]",
|
||||
homeXlarge:
|
||||
"px-[var(--spacing-scale-016)] py-[var(--spacing-scale-016)] gap-[var(--spacing-scale-004)] h-[44px]",
|
||||
xlarge:
|
||||
"px-[var(--spacing-scale-016)] py-[var(--spacing-scale-008)] gap-[var(--spacing-scale-004)] h-[44px]",
|
||||
};
|
||||
// Interactive styles
|
||||
const interactiveStyles =
|
||||
"hover:scale-[1.02] active:scale-[0.98] focus:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100";
|
||||
|
||||
const smallTextStyle =
|
||||
"font-inter text-[10px] leading-[12px] font-medium tracking-[0%]";
|
||||
const mediumTextStyle =
|
||||
"font-inter text-[12px] leading-[14px] font-medium tracking-[0%]";
|
||||
const largeTextStyle =
|
||||
"font-inter text-[16px] leading-[20px] font-medium tracking-[0%]";
|
||||
const xlargeTextStyle =
|
||||
"font-inter text-[24px] leading-[28px] font-normal tracking-[0%]";
|
||||
// Disabled styles
|
||||
const disabledStyles = disabled
|
||||
? "bg-[var(--color-surface-default-tertiary)] text-[var(--color-content-default-tertiary)]"
|
||||
: "";
|
||||
|
||||
const textStyles: Record<string, string> = {
|
||||
default: smallTextStyle,
|
||||
xsmall: smallTextStyle,
|
||||
xsmallUseCases: smallTextStyle,
|
||||
home: smallTextStyle,
|
||||
homeMd: mediumTextStyle,
|
||||
homeUseCases: mediumTextStyle,
|
||||
large: largeTextStyle,
|
||||
largeUseCases: largeTextStyle,
|
||||
homeXlarge: xlargeTextStyle,
|
||||
xlarge: xlargeTextStyle,
|
||||
};
|
||||
|
||||
const baseStyles = `inline-flex items-center ${sizeStyles[size]} rounded-[var(--radius-measures-radius-full)] ${textStyles[size]} transition-all duration-200 ease-in-out cursor-pointer focus:scale-[1.02]`;
|
||||
|
||||
let finalVariant = variant;
|
||||
if (disabled) {
|
||||
finalVariant = "default";
|
||||
}
|
||||
|
||||
const combinedStyles = `${baseStyles} ${variantStyles[finalVariant]} ${
|
||||
finalVariant === "home"
|
||||
? homeOutlineStyles[size]
|
||||
: activeOutlineStyles[size]
|
||||
} ${isActive ? activeStateStyles[size] : ""} ${className}`;
|
||||
// Combine all styles
|
||||
const combinedStyles = `${baseStyles} ${stateStyles[state]} ${interactiveStyles} ${disabledStyles} ${className}`;
|
||||
|
||||
const accessibilityProps = {
|
||||
...(ariaLabel && { "aria-label": ariaLabel }),
|
||||
...(disabled && { "aria-disabled": true }),
|
||||
...(state === "selected" && { "aria-current": "page" as const }),
|
||||
role: "menuitem" as const,
|
||||
tabIndex: disabled ? -1 : 0,
|
||||
...props,
|
||||
|
||||
@@ -1,43 +1,50 @@
|
||||
export type MenuBarItemSizeValue =
|
||||
| "default"
|
||||
| "xsmall"
|
||||
| "xsmallUseCases"
|
||||
| "home"
|
||||
| "homeMd"
|
||||
| "homeUseCases"
|
||||
| "large"
|
||||
| "largeUseCases"
|
||||
| "homeXlarge"
|
||||
| "xlarge"
|
||||
| "Default"
|
||||
| "XSmall"
|
||||
| "XSmallUseCases"
|
||||
| "Home"
|
||||
| "HomeMd"
|
||||
| "HomeUseCases"
|
||||
| "X Small"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "LargeUseCases"
|
||||
| "HomeXlarge"
|
||||
| "XLarge";
|
||||
| "X Large";
|
||||
|
||||
export type MenuBarItemVariantValue = "default" | "home" | "Default" | "Home";
|
||||
export type MenuBarItemStateValue =
|
||||
| "default"
|
||||
| "hover"
|
||||
| "selected";
|
||||
|
||||
export type MenuBarItemModeValue =
|
||||
| "default"
|
||||
| "inverse";
|
||||
|
||||
export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Menu bar item variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Menu bar item state: "default", "hover", or "selected".
|
||||
* @default "default"
|
||||
*/
|
||||
variant?: MenuBarItemVariantValue;
|
||||
state?: MenuBarItemStateValue;
|
||||
/**
|
||||
* Menu bar item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Menu bar 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;
|
||||
/**
|
||||
* 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".
|
||||
* @default "X Small"
|
||||
*/
|
||||
size?: MenuBarItemSizeValue;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
isActive?: boolean;
|
||||
/**
|
||||
* Whether to use reduced padding (for "use cases" button).
|
||||
* @default false
|
||||
*/
|
||||
reducedPadding?: boolean;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user