import * as React from 'react' import { ChevronRight, ChevronLeft, LayoutGrid, type LucideIcon, } from 'lucide-react' import { ImageField } from '../Image/ImageField' import type { SAILSemanticColor } from '../../types/sail' /** * Represents a single page/tab in the site navigation. * Maps to SAIL's NavigationNode / NavigationMenuTab types. */ export interface SiteNavPage { /** Display text for the page (maps to NavigationNode.name / NavigationMenuTab.label) */ label: string /** Lucide icon component (maps to NavigationNode.icon / NavigationMenuTab.iconFriendlyName) */ icon?: LucideIcon /** Whether this page is currently active (maps to NavigationMenuTab.isSelected) */ isSelected?: boolean /** Whether this is a group with children (maps to NavigationNode.isGroup) */ isGroup?: boolean /** Sub-pages for grouped navigation (maps to NavigationNode.children) */ children?: SiteNavPage[] /** Optional badge text, e.g. "(1)" for unread count */ badge?: string /** Callback when page is clicked */ onClick?: () => void } /** * Props for the SiteNav component. * Maps to SAIL's a!navigationLayout with primaryNavLayoutType="SIDEBAR". */ export interface SiteNavProps { /** Site/solution display name (maps to NavigationLayout.displayName) */ displayName?: string /** Array of site pages (maps to NavigationLayout.tabs / NavigationNode[]) */ pages: SiteNavPage[] /** Controlled collapsed state */ collapsed?: boolean /** Callback when collapse toggle is clicked */ onCollapseToggle?: (collapsed: boolean) => void /** Whether to show the navigation (maps to NavigationLayout.showNavigation) */ showNavigation?: boolean /** User full name — initials are derived automatically */ userName?: string /** Path to Appian logo image */ appianLogoSrc?: string /** Background color for the selected/highlighted page. Accepts hex or semantic color. Default: "POSITIVE" */ highlightColor?: SAILSemanticColor | string } /** * SiteNav Component * * Renders the sidebar navigation for an Appian site. * Maps to SAIL's a!navigationLayout with primaryNavLayoutType="SIDEBAR". */ export const SiteNav: React.FC = ({ displayName, pages, collapsed: controlledCollapsed, onCollapseToggle, showNavigation = true, userName, appianLogoSrc = 'images/icon-appian-header.png', highlightColor = 'POSITIVE', }) => { const [internalCollapsed, setInternalCollapsed] = React.useState(false) const isCollapsed = controlledCollapsed ?? internalCollapsed // Highlight color mappings const semanticHighlightMap: Record = { ACCENT: 'bg-blue-100', POSITIVE: 'bg-green-100', NEGATIVE: 'bg-red-100', SECONDARY: 'bg-gray-100', STANDARD: 'bg-gray-200', } const isHexHighlight = highlightColor.startsWith('#') const highlightClass = !isHexHighlight ? (semanticHighlightMap[highlightColor as SAILSemanticColor] || semanticHighlightMap.POSITIVE) : '' const highlightStyle = isHexHighlight ? { backgroundColor: highlightColor } : undefined const userInitials = userName ? userName.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2) : 'U' const toggleCollapse = () => { const next = !isCollapsed setInternalCollapsed(next) onCollapseToggle?.(next) } if (!showNavigation) return null return ( ) } /** * SiteNavItem — renders a single navigation item (page or group). */ const SiteNavItem: React.FC<{ page: SiteNavPage isCollapsed: boolean highlightClass: string highlightStyle?: React.CSSProperties depth?: number }> = ({ page, isCollapsed, highlightClass, highlightStyle, depth = 0 }) => { const [expanded, setExpanded] = React.useState( // Auto-expand if any child is selected page.children?.some((c) => c.isSelected) ?? false ) const IconComponent = page.icon const hasChildren = page.isGroup || (page.children && page.children.length > 0) const handleClick = () => { if (hasChildren) { setExpanded((prev) => !prev) } page.onClick?.() } return (
  • {/* Children */} {hasChildren && expanded && !isCollapsed && page.children && (
      {page.children.map((child) => ( ))}
    )}
  • ) }