|
|
@@ -53,6 +53,10 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
/// Flipped so short result lists stay visually under the search bar instead of leaving a gap above the cards.
|
|
|
private let jobListingsContainer = JobListingsDocumentView()
|
|
|
private let jobListingsStack = NSStackView()
|
|
|
+ /// Shown when a sidebar item other than Home is selected.
|
|
|
+ private let nonHomeHost = NSView()
|
|
|
+ private let nonHomeTitleLabel = NSTextField(labelWithString: "")
|
|
|
+ private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
|
|
|
|
|
|
private var currentSidebarItems: [SidebarItem] = []
|
|
|
private var selectedSidebarIndex: Int = 0
|
|
|
@@ -87,6 +91,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
configureSidebar()
|
|
|
catalogJobListings = data.jobListings
|
|
|
configureJobListings([], noResultsForQuery: nil)
|
|
|
+ updateMainContentVisibility()
|
|
|
}
|
|
|
|
|
|
private func setupLayout() {
|
|
|
@@ -126,6 +131,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
mainHost.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
|
|
|
mainHost.addSubview(mainOverlay)
|
|
|
+ configureNonHomePlaceholder()
|
|
|
+ mainHost.addSubview(nonHomeHost)
|
|
|
|
|
|
mainOverlay.orientation = .vertical
|
|
|
mainOverlay.spacing = 0
|
|
|
@@ -219,6 +226,11 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
mainOverlay.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
|
mainOverlay.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
|
|
|
|
+ nonHomeHost.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
|
|
|
+ nonHomeHost.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
|
|
|
+ nonHomeHost.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
|
+ nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
|
+
|
|
|
searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
|
jobListingsScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
|
|
|
|
@@ -485,6 +497,62 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ private func configureNonHomePlaceholder() {
|
|
|
+ nonHomeHost.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ nonHomeHost.wantsLayer = true
|
|
|
+ nonHomeHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ nonHomeHost.isHidden = true
|
|
|
+
|
|
|
+ nonHomeTitleLabel.font = .systemFont(ofSize: 22, weight: .bold)
|
|
|
+ nonHomeTitleLabel.textColor = Theme.primaryText
|
|
|
+ nonHomeTitleLabel.alignment = .center
|
|
|
+ nonHomeTitleLabel.maximumNumberOfLines = 1
|
|
|
+
|
|
|
+ nonHomeSubtitleLabel.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
+ nonHomeSubtitleLabel.textColor = Theme.secondaryText
|
|
|
+ nonHomeSubtitleLabel.alignment = .center
|
|
|
+ nonHomeSubtitleLabel.maximumNumberOfLines = 0
|
|
|
+ nonHomeSubtitleLabel.stringValue = "This area is not available in the preview build. Use Home to search jobs."
|
|
|
+
|
|
|
+ let stack = NSStackView(views: [nonHomeTitleLabel, nonHomeSubtitleLabel])
|
|
|
+ stack.orientation = .vertical
|
|
|
+ stack.spacing = 10
|
|
|
+ stack.alignment = .centerX
|
|
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ nonHomeHost.addSubview(stack)
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ stack.centerXAnchor.constraint(equalTo: nonHomeHost.centerXAnchor),
|
|
|
+ stack.centerYAnchor.constraint(equalTo: nonHomeHost.centerYAnchor),
|
|
|
+ stack.leadingAnchor.constraint(greaterThanOrEqualTo: nonHomeHost.leadingAnchor, constant: 32),
|
|
|
+ stack.trailingAnchor.constraint(lessThanOrEqualTo: nonHomeHost.trailingAnchor, constant: -32),
|
|
|
+ nonHomeSubtitleLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 420)
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ private func isHomeSidebarIndex(_ index: Int) -> Bool {
|
|
|
+ guard index >= 0, index < currentSidebarItems.count else { return false }
|
|
|
+ return currentSidebarItems[index].title == "Home"
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateMainContentVisibility() {
|
|
|
+ let home = isHomeSidebarIndex(selectedSidebarIndex)
|
|
|
+ mainOverlay.isHidden = !home
|
|
|
+ nonHomeHost.isHidden = home
|
|
|
+ if !home, selectedSidebarIndex < currentSidebarItems.count {
|
|
|
+ nonHomeTitleLabel.stringValue = currentSidebarItems[selectedSidebarIndex].title
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Restores the main job-search experience: cleared query and no listings until the user searches again.
|
|
|
+ private func applyHomeState() {
|
|
|
+ jobKeywordsField.stringValue = ""
|
|
|
+ configureJobListings([], noResultsForQuery: nil)
|
|
|
+ window?.makeFirstResponder(nil)
|
|
|
+ if let doc = jobListingsScrollView.documentView {
|
|
|
+ doc.scroll(NSPoint(x: 0, y: 0))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private func updateSearchBarShadowPath() {
|
|
|
guard searchBarShadowHost.bounds.width > 0, searchBarShadowHost.bounds.height > 0 else { return }
|
|
|
let r = searchBarShadowHost.bounds
|
|
|
@@ -726,9 +794,20 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
}
|
|
|
|
|
|
private func selectSidebarItem(at index: Int) {
|
|
|
- guard index >= 0, index < currentSidebarItems.count, index != selectedSidebarIndex else { return }
|
|
|
+ guard index >= 0, index < currentSidebarItems.count else { return }
|
|
|
+ let selectingHome = isHomeSidebarIndex(index)
|
|
|
+ if index == selectedSidebarIndex {
|
|
|
+ if selectingHome {
|
|
|
+ applyHomeState()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
selectedSidebarIndex = index
|
|
|
configureSidebar()
|
|
|
+ updateMainContentVisibility()
|
|
|
+ if selectingHome {
|
|
|
+ applyHomeState()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
}
|