浏览代码

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 3 周之前
父节点
当前提交
87da1140f1
共有 3 个文件被更改,包括 100 次插入11 次删除
  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 @@
1
+# Xcode / Swift
2
+.DS_Store
3
+DerivedData/
4
+.derivedData/
5
+build/
6
+*.xcuserstate
7
+xcuserdata/
8
+
9
+# SwiftPM
10
+.build/
11
+.swiftpm/

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

@@ -37,6 +37,9 @@ final class DashboardView: NSView {
37 37
     private let searchField = NSTextField()
38 38
     private let scrollView = NSScrollView()
39 39
 
40
+    private var currentSidebarItems: [SidebarItem] = []
41
+    private var selectedSidebarIndex: Int = 0
42
+
40 43
     override init(frame frameRect: NSRect) {
41 44
         super.init(frame: frameRect)
42 45
         setupLayout()
@@ -55,7 +58,11 @@ final class DashboardView: NSView {
55 58
     func render(_ data: DashboardData) {
56 59
         greetingLabel.stringValue = "Welcome"
57 60
         subtitleLabel.stringValue = data.subtitle
58
-        configureSidebar(data.sidebarItems)
61
+        currentSidebarItems = data.sidebarItems
62
+        if selectedSidebarIndex >= currentSidebarItems.count {
63
+            selectedSidebarIndex = max(0, currentSidebarItems.count - 1)
64
+        }
65
+        configureSidebar()
59 66
         updateDocumentLayout()
60 67
     }
61 68
 
@@ -236,7 +243,8 @@ final class DashboardView: NSView {
236 243
         // Hook up search submission here when wiring up real data.
237 244
     }
238 245
 
239
-    private func configureSidebar(_ items: [SidebarItem]) {
246
+    private func configureSidebar() {
247
+        let items = currentSidebarItems
240 248
         sidebar.arrangedSubviews.forEach {
241 249
             sidebar.removeArrangedSubview($0)
242 250
             $0.removeFromSuperview()
@@ -254,17 +262,26 @@ final class DashboardView: NSView {
254 262
         sidebar.addArrangedSubview(titleToMenuSpacer)
255 263
 
256 264
         items.enumerated().forEach { index, item in
265
+            let isSelected = index == selectedSidebarIndex
266
+
267
+            let rowHost = SidebarNavRowView { [weak self] in
268
+                self?.selectSidebarItem(at: index)
269
+            }
270
+            rowHost.translatesAutoresizingMaskIntoConstraints = false
271
+            rowHost.wantsLayer = true
272
+            rowHost.layer?.cornerRadius = 8
273
+            if isSelected {
274
+                rowHost.layer?.backgroundColor = Theme.selectionFill.cgColor
275
+            }
276
+            rowHost.setAccessibilityLabel(item.title)
277
+            rowHost.setAccessibilityRole(.button)
278
+            rowHost.setAccessibilitySelected(isSelected)
279
+
257 280
             let row = NSStackView()
258 281
             row.orientation = .horizontal
259 282
             row.spacing = 8
260 283
             row.alignment = .centerY
261
-            row.wantsLayer = true
262
-            row.layer?.cornerRadius = 8
263
-            row.edgeInsets = NSEdgeInsets(top: 8, left: 10, bottom: 8, right: 10)
264
-            let isSelected = index == 0
265
-            if isSelected {
266
-                row.layer?.backgroundColor = Theme.selectionFill.cgColor
267
-            }
284
+            row.translatesAutoresizingMaskIntoConstraints = false
268 285
 
269 286
             let icon = NSImageView()
270 287
             icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
@@ -274,6 +291,7 @@ final class DashboardView: NSView {
274 291
             let text = NSTextField(labelWithString: item.title)
275 292
             text.font = .systemFont(ofSize: 14, weight: .medium)
276 293
             text.textColor = isSelected ? Theme.primaryText : Theme.secondaryText
294
+            text.refusesFirstResponder = true
277 295
 
278 296
             row.addArrangedSubview(icon)
279 297
             row.addArrangedSubview(text)
@@ -288,12 +306,21 @@ final class DashboardView: NSView {
288 306
                 badgeField.alignment = .center
289 307
                 badgeField.maximumNumberOfLines = 1
290 308
                 badgeField.lineBreakMode = .byClipping
309
+                badgeField.refusesFirstResponder = true
291 310
                 badgeField.translatesAutoresizingMaskIntoConstraints = false
292 311
                 badgeField.widthAnchor.constraint(equalToConstant: 42).isActive = true
293 312
                 row.addArrangedSubview(NSView())
294 313
                 row.addArrangedSubview(badgeField)
295 314
             }
296
-            sidebar.addArrangedSubview(row)
315
+
316
+            rowHost.addSubview(row)
317
+            NSLayoutConstraint.activate([
318
+                row.leadingAnchor.constraint(equalTo: rowHost.leadingAnchor, constant: 10),
319
+                row.trailingAnchor.constraint(equalTo: rowHost.trailingAnchor, constant: -10),
320
+                row.topAnchor.constraint(equalTo: rowHost.topAnchor, constant: 8),
321
+                row.bottomAnchor.constraint(equalTo: rowHost.bottomAnchor, constant: -8)
322
+            ])
323
+            sidebar.addArrangedSubview(rowHost)
297 324
         }
298 325
 
299 326
         let sidebarBottomSpacer = NSView()
@@ -392,9 +419,61 @@ final class DashboardView: NSView {
392 419
         NSWorkspace.shared.open(url)
393 420
     }
394 421
 
422
+    private func selectSidebarItem(at index: Int) {
423
+        guard index >= 0, index < currentSidebarItems.count, index != selectedSidebarIndex else { return }
424
+        selectedSidebarIndex = index
425
+        configureSidebar()
426
+    }
427
+
395 428
     private func updateDocumentLayout() {
396 429
         documentContainer.layoutSubtreeIfNeeded()
397 430
         let fittingHeight = max(chromeContainer.fittingSize.height, bounds.height)
398 431
         documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
399 432
     }
400 433
 }
434
+
435
+/// Captures clicks for the full sidebar pill so icon, label, and padding behave as one tab.
436
+private final class SidebarNavRowView: NSView {
437
+    private let onSelect: () -> Void
438
+
439
+    init(onSelect: @escaping () -> Void) {
440
+        self.onSelect = onSelect
441
+        super.init(frame: .zero)
442
+    }
443
+
444
+    @available(*, unavailable)
445
+    required init?(coder: NSCoder) {
446
+        fatalError("init(coder:) has not been implemented")
447
+    }
448
+
449
+    override func hitTest(_ point: NSPoint) -> NSView? {
450
+        guard let superview else { return super.hitTest(point) }
451
+        let local = convert(point, from: superview)
452
+        return bounds.contains(local) ? self : nil
453
+    }
454
+
455
+    override func mouseDown(with event: NSEvent) {
456
+        onSelect()
457
+    }
458
+
459
+    override func updateTrackingAreas() {
460
+        super.updateTrackingAreas()
461
+        trackingAreas.forEach { removeTrackingArea($0) }
462
+        addTrackingArea(NSTrackingArea(
463
+            rect: bounds,
464
+            options: [.activeInKeyWindow, .mouseEnteredAndExited, .inVisibleRect],
465
+            owner: self,
466
+            userInfo: nil
467
+        ))
468
+    }
469
+
470
+    override func mouseEntered(with event: NSEvent) {
471
+        super.mouseEntered(with: event)
472
+        NSCursor.pointingHand.push()
473
+    }
474
+
475
+    override func mouseExited(with event: NSEvent) {
476
+        super.mouseExited(with: event)
477
+        NSCursor.pop()
478
+    }
479
+}

+ 0 - 1
app_for_indeed

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