Browse Source

Clean up repo and improve dashboard sidebar

Add a .gitignore for Xcode and SwiftPM build output so local DerivedData and similar paths stay out of version control. Remove the stray app_for_indeed directory that duplicated the root README and embedded a nested repository.

Update DashboardView so sidebar navigation keeps a selected index across renders, uses a tappable row host for the full pill hit target, and exposes clearer accessibility roles for each item.
AhtashamShahzad1 2 tháng trước cách đây
mục cha
commit
87da1140f1
3 tập tin đã thay đổi với 100 bổ sung11 xóa
  1. 11 0
      .gitignore
  2. 89 10
      App for Indeed/Views/DashboardView.swift
  3. 0 1
      app_for_indeed

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+# Xcode / Swift
+.DS_Store
+DerivedData/
+.derivedData/
+build/
+*.xcuserstate
+xcuserdata/
+
+# SwiftPM
+.build/
+.swiftpm/

+ 89 - 10
App for Indeed/Views/DashboardView.swift

@@ -37,6 +37,9 @@ final class DashboardView: NSView {
     private let searchField = NSTextField()
     private let scrollView = NSScrollView()
 
+    private var currentSidebarItems: [SidebarItem] = []
+    private var selectedSidebarIndex: Int = 0
+
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
         setupLayout()
@@ -55,7 +58,11 @@ final class DashboardView: NSView {
     func render(_ data: DashboardData) {
         greetingLabel.stringValue = "Welcome"
         subtitleLabel.stringValue = data.subtitle
-        configureSidebar(data.sidebarItems)
+        currentSidebarItems = data.sidebarItems
+        if selectedSidebarIndex >= currentSidebarItems.count {
+            selectedSidebarIndex = max(0, currentSidebarItems.count - 1)
+        }
+        configureSidebar()
         updateDocumentLayout()
     }
 
@@ -236,7 +243,8 @@ final class DashboardView: NSView {
         // Hook up search submission here when wiring up real data.
     }
 
-    private func configureSidebar(_ items: [SidebarItem]) {
+    private func configureSidebar() {
+        let items = currentSidebarItems
         sidebar.arrangedSubviews.forEach {
             sidebar.removeArrangedSubview($0)
             $0.removeFromSuperview()
@@ -254,17 +262,26 @@ final class DashboardView: NSView {
         sidebar.addArrangedSubview(titleToMenuSpacer)
 
         items.enumerated().forEach { index, item in
+            let isSelected = index == selectedSidebarIndex
+
+            let rowHost = SidebarNavRowView { [weak self] in
+                self?.selectSidebarItem(at: index)
+            }
+            rowHost.translatesAutoresizingMaskIntoConstraints = false
+            rowHost.wantsLayer = true
+            rowHost.layer?.cornerRadius = 8
+            if isSelected {
+                rowHost.layer?.backgroundColor = Theme.selectionFill.cgColor
+            }
+            rowHost.setAccessibilityLabel(item.title)
+            rowHost.setAccessibilityRole(.button)
+            rowHost.setAccessibilitySelected(isSelected)
+
             let row = NSStackView()
             row.orientation = .horizontal
             row.spacing = 8
             row.alignment = .centerY
-            row.wantsLayer = true
-            row.layer?.cornerRadius = 8
-            row.edgeInsets = NSEdgeInsets(top: 8, left: 10, bottom: 8, right: 10)
-            let isSelected = index == 0
-            if isSelected {
-                row.layer?.backgroundColor = Theme.selectionFill.cgColor
-            }
+            row.translatesAutoresizingMaskIntoConstraints = false
 
             let icon = NSImageView()
             icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
@@ -274,6 +291,7 @@ final class DashboardView: NSView {
             let text = NSTextField(labelWithString: item.title)
             text.font = .systemFont(ofSize: 14, weight: .medium)
             text.textColor = isSelected ? Theme.primaryText : Theme.secondaryText
+            text.refusesFirstResponder = true
 
             row.addArrangedSubview(icon)
             row.addArrangedSubview(text)
@@ -288,12 +306,21 @@ final class DashboardView: NSView {
                 badgeField.alignment = .center
                 badgeField.maximumNumberOfLines = 1
                 badgeField.lineBreakMode = .byClipping
+                badgeField.refusesFirstResponder = true
                 badgeField.translatesAutoresizingMaskIntoConstraints = false
                 badgeField.widthAnchor.constraint(equalToConstant: 42).isActive = true
                 row.addArrangedSubview(NSView())
                 row.addArrangedSubview(badgeField)
             }
-            sidebar.addArrangedSubview(row)
+
+            rowHost.addSubview(row)
+            NSLayoutConstraint.activate([
+                row.leadingAnchor.constraint(equalTo: rowHost.leadingAnchor, constant: 10),
+                row.trailingAnchor.constraint(equalTo: rowHost.trailingAnchor, constant: -10),
+                row.topAnchor.constraint(equalTo: rowHost.topAnchor, constant: 8),
+                row.bottomAnchor.constraint(equalTo: rowHost.bottomAnchor, constant: -8)
+            ])
+            sidebar.addArrangedSubview(rowHost)
         }
 
         let sidebarBottomSpacer = NSView()
@@ -392,9 +419,61 @@ final class DashboardView: NSView {
         NSWorkspace.shared.open(url)
     }
 
+    private func selectSidebarItem(at index: Int) {
+        guard index >= 0, index < currentSidebarItems.count, index != selectedSidebarIndex else { return }
+        selectedSidebarIndex = index
+        configureSidebar()
+    }
+
     private func updateDocumentLayout() {
         documentContainer.layoutSubtreeIfNeeded()
         let fittingHeight = max(chromeContainer.fittingSize.height, bounds.height)
         documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
     }
 }
+
+/// Captures clicks for the full sidebar pill so icon, label, and padding behave as one tab.
+private final class SidebarNavRowView: NSView {
+    private let onSelect: () -> Void
+
+    init(onSelect: @escaping () -> Void) {
+        self.onSelect = onSelect
+        super.init(frame: .zero)
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func hitTest(_ point: NSPoint) -> NSView? {
+        guard let superview else { return super.hitTest(point) }
+        let local = convert(point, from: superview)
+        return bounds.contains(local) ? self : nil
+    }
+
+    override func mouseDown(with event: NSEvent) {
+        onSelect()
+    }
+
+    override func updateTrackingAreas() {
+        super.updateTrackingAreas()
+        trackingAreas.forEach { removeTrackingArea($0) }
+        addTrackingArea(NSTrackingArea(
+            rect: bounds,
+            options: [.activeInKeyWindow, .mouseEnteredAndExited, .inVisibleRect],
+            owner: self,
+            userInfo: nil
+        ))
+    }
+
+    override func mouseEntered(with event: NSEvent) {
+        super.mouseEntered(with: event)
+        NSCursor.pointingHand.push()
+    }
+
+    override func mouseExited(with event: NSEvent) {
+        super.mouseExited(with: event)
+        NSCursor.pop()
+    }
+}

+ 0 - 1
app_for_indeed

@@ -1 +0,0 @@
-Subproject commit 739c40a324e7bf07f4df03c69d916dd8a37475b6