import Cocoa enum SidebarDestination: Int, CaseIterable { case home case scan case scanAndHome case settings } final class SidebarView: NSView { var onDestinationSelected: ((SidebarDestination) -> Void)? private var navItems: [SidebarNavItem] = [] init() { super.init(frame: .zero) wantsLayer = true layer?.backgroundColor = AppTheme.sidebarBackground.cgColor translatesAutoresizingMaskIntoConstraints = false setup() } @available(*, unavailable) required init?(coder: NSCoder) { nil } func select(_ destination: SidebarDestination) { for (index, item) in navItems.enumerated() { item.isSelected = index == destination.rawValue } } private func setup() { let logoContainer = NSView() logoContainer.translatesAutoresizingMaskIntoConstraints = false let logoIcon = NSImageView() logoIcon.translatesAutoresizingMaskIntoConstraints = false if let image = NSImage(systemSymbolName: "printer.fill", accessibilityDescription: "Smart Printer") { let config = NSImage.SymbolConfiguration(pointSize: 28, weight: .medium) logoIcon.image = image.withSymbolConfiguration(config) } logoIcon.contentTintColor = AppTheme.purple let appNameLabel = NSTextField(labelWithString: "Smart Printer") appNameLabel.font = AppTheme.semiboldFont(size: 15) appNameLabel.textColor = AppTheme.textPrimary appNameLabel.alignment = .center appNameLabel.translatesAutoresizingMaskIntoConstraints = false let navStack = NSStackView() navStack.orientation = .vertical navStack.spacing = 4 navStack.alignment = .leading navStack.translatesAutoresizingMaskIntoConstraints = false let bottomNavStack = NSStackView() bottomNavStack.orientation = .vertical bottomNavStack.spacing = 4 bottomNavStack.alignment = .leading bottomNavStack.translatesAutoresizingMaskIntoConstraints = false let homeItem = SidebarNavItem(title: "Home", symbolName: "house.fill", isSelected: true) let scanItem = SidebarNavItem(title: "Scan", symbolName: "viewfinder") let scanAndHomeItem = SidebarNavItem(title: "Premium", symbolName: "diamond.fill", accent: .premium) let settingsItem = SidebarNavItem(title: "Settings", symbolName: "gearshape") let mainNavItems = [homeItem, scanItem, scanAndHomeItem] navItems = mainNavItems + [settingsItem] for (index, item) in navItems.enumerated() { item.onClick = { [weak self] in guard let self, let destination = SidebarDestination(rawValue: index) else { return } self.select(destination) self.onDestinationSelected?(destination) } } for item in mainNavItems { navStack.addArrangedSubview(item) } bottomNavStack.addArrangedSubview(settingsItem) addSubview(logoContainer) logoContainer.addSubview(logoIcon) addSubview(appNameLabel) addSubview(navStack) addSubview(bottomNavStack) let divider = NSBox() divider.boxType = .separator divider.translatesAutoresizingMaskIntoConstraints = false addSubview(divider) NSLayoutConstraint.activate([ widthAnchor.constraint(equalToConstant: AppTheme.sidebarWidth), logoContainer.topAnchor.constraint(equalTo: topAnchor, constant: 52), logoContainer.centerXAnchor.constraint(equalTo: centerXAnchor), logoContainer.widthAnchor.constraint(equalToConstant: 48), logoContainer.heightAnchor.constraint(equalToConstant: 48), logoIcon.centerXAnchor.constraint(equalTo: logoContainer.centerXAnchor), logoIcon.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor), logoIcon.widthAnchor.constraint(equalToConstant: 36), logoIcon.heightAnchor.constraint(equalToConstant: 36), appNameLabel.topAnchor.constraint(equalTo: logoContainer.bottomAnchor, constant: 8), appNameLabel.centerXAnchor.constraint(equalTo: centerXAnchor), navStack.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 32), navStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), navStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12), bottomNavStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), bottomNavStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12), bottomNavStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24), homeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor), scanItem.widthAnchor.constraint(equalTo: navStack.widthAnchor), scanAndHomeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor), settingsItem.widthAnchor.constraint(equalTo: bottomNavStack.widthAnchor), divider.trailingAnchor.constraint(equalTo: trailingAnchor), divider.topAnchor.constraint(equalTo: topAnchor), divider.bottomAnchor.constraint(equalTo: bottomAnchor), divider.widthAnchor.constraint(equalToConstant: 1), ]) } }