|
|
@@ -20,47 +20,40 @@ private enum PremiumSheetLayout {
|
|
|
}
|
|
|
|
|
|
final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate {
|
|
|
- /// Indeed.com-inspired neutrals and brand blue (white surfaces, `#2557a7` accent, `#2d2d2d` / `#767676` text, `#d4d2d0` borders).
|
|
|
+ /// Indeed.com-inspired neutrals and brand blue; values follow light / dark / system appearance.
|
|
|
private enum Theme {
|
|
|
- static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
|
- static let pageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- static let chromeBackground = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
|
|
|
- static let sidebarBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- static let mainHostBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- /// Welcome hero (matches reference: `#0052CC` heading, `#334155` subline, `#EFF6FF` icon well).
|
|
|
- static let welcomeHeroHeadingBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
|
|
|
- static let welcomeHeroSubtitleText = NSColor(srgbRed: 51 / 255, green: 65 / 255, blue: 85 / 255, alpha: 1)
|
|
|
- static let welcomeHeroIconWell = NSColor(srgbRed: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1)
|
|
|
- /// Light decorative strokes / sparkles behind the welcome hero.
|
|
|
- static let welcomeHeroWaveTint = NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
|
|
|
- /// Subtitle on the welcome hero: dark neutral gray to match the reference layout.
|
|
|
- static let welcomeSubtitleText = NSColor(srgbRed: 64 / 255, green: 64 / 255, blue: 64 / 255, alpha: 1)
|
|
|
- static let selectionFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.12)
|
|
|
- static let cardBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- static let toggleBackground = NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
|
|
|
- static let primaryText = NSColor(srgbRed: 45 / 255, green: 45 / 255, blue: 45 / 255, alpha: 1)
|
|
|
- static let secondaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
|
|
|
- static let tertiaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
|
|
|
- static let border = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
|
|
|
- static let featureIconWell = NSColor(srgbRed: 220 / 255, green: 235 / 255, blue: 252 / 255, alpha: 1)
|
|
|
- /// Job search bar outer stroke (soft blue-gray, pill field in the reference UI).
|
|
|
- static let searchBarBorder = NSColor(srgbRed: 180 / 255, green: 200 / 255, blue: 228 / 255, alpha: 1)
|
|
|
- /// Search bar border on hover (brand-tinted, matches focus affordance elsewhere).
|
|
|
- static let searchBarBorderHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
|
|
|
- static let proCardFill = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
|
|
|
- static let proCardBorder = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
|
|
|
- static let proAccent = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
|
- static let proCTABackground = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
|
- static let proCTAText = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- /// Hover states: darker brand blue, stronger tints, and subtle neutral fills used across CTAs, toggles, and the sidebar.
|
|
|
- static let brandBlueHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
|
|
|
- static let selectionFillHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.2)
|
|
|
- static let neutralHoverFill = NSColor(srgbRed: 240 / 255, green: 240 / 255, blue: 240 / 255, alpha: 1)
|
|
|
- static let sidebarRowHoverFill = NSColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.04)
|
|
|
- static let settingsPageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- static let settingsGroupBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
- static let settingsIconBackground = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
|
|
|
- static let settingsDivider = NSColor(srgbRed: 228 / 255, green: 228 / 255, blue: 228 / 255, alpha: 1)
|
|
|
+ static var brandBlue: NSColor { AppDashboardTheme.brandBlue }
|
|
|
+ static var pageBackground: NSColor { AppDashboardTheme.pageBackground }
|
|
|
+ static var chromeBackground: NSColor { AppDashboardTheme.chromeBackground }
|
|
|
+ static var sidebarBackground: NSColor { AppDashboardTheme.sidebarBackground }
|
|
|
+ static var mainHostBackground: NSColor { AppDashboardTheme.mainHostBackground }
|
|
|
+ static var welcomeHeroHeadingBlue: NSColor { AppDashboardTheme.welcomeHeroHeadingBlue }
|
|
|
+ static var welcomeHeroSubtitleText: NSColor { AppDashboardTheme.welcomeHeroSubtitleText }
|
|
|
+ static var welcomeHeroIconWell: NSColor { AppDashboardTheme.welcomeHeroIconWell }
|
|
|
+ static var welcomeHeroWaveTint: NSColor { AppDashboardTheme.welcomeHeroWaveTint }
|
|
|
+ static var welcomeSubtitleText: NSColor { AppDashboardTheme.welcomeSubtitleText }
|
|
|
+ static var selectionFill: NSColor { AppDashboardTheme.selectionFill }
|
|
|
+ static var cardBackground: NSColor { AppDashboardTheme.cardBackground }
|
|
|
+ static var toggleBackground: NSColor { AppDashboardTheme.toggleBackground }
|
|
|
+ static var primaryText: NSColor { AppDashboardTheme.primaryText }
|
|
|
+ static var secondaryText: NSColor { AppDashboardTheme.secondaryText }
|
|
|
+ static var tertiaryText: NSColor { AppDashboardTheme.tertiaryText }
|
|
|
+ static var border: NSColor { AppDashboardTheme.border }
|
|
|
+ static var searchBarBorder: NSColor { AppDashboardTheme.searchBarBorder }
|
|
|
+ static var searchBarBorderHover: NSColor { AppDashboardTheme.searchBarBorderHover }
|
|
|
+ static var proCardFill: NSColor { AppDashboardTheme.proCardFill }
|
|
|
+ static var proCardBorder: NSColor { AppDashboardTheme.proCardBorder }
|
|
|
+ static var proAccent: NSColor { AppDashboardTheme.proAccent }
|
|
|
+ static var proCTABackground: NSColor { AppDashboardTheme.proCTABackground }
|
|
|
+ static var proCTAText: NSColor { AppDashboardTheme.proCTAText }
|
|
|
+ static var brandBlueHover: NSColor { AppDashboardTheme.brandBlueHover }
|
|
|
+ static var selectionFillHover: NSColor { AppDashboardTheme.selectionFillHover }
|
|
|
+ static var neutralHoverFill: NSColor { AppDashboardTheme.neutralHoverFill }
|
|
|
+ static var sidebarRowHoverFill: NSColor { AppDashboardTheme.sidebarRowHoverFill }
|
|
|
+ static var settingsPageBackground: NSColor { AppDashboardTheme.settingsPageBackground }
|
|
|
+ static var settingsGroupBackground: NSColor { AppDashboardTheme.settingsGroupBackground }
|
|
|
+ static var settingsIconBackground: NSColor { AppDashboardTheme.settingsIconBackground }
|
|
|
+ static var settingsDivider: NSColor { AppDashboardTheme.settingsDivider }
|
|
|
}
|
|
|
|
|
|
/// Multiline `NSTextField` often ignores `alignment` for wrapped runs; explicit paragraph alignment matches the title.
|
|
|
@@ -163,6 +156,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
private weak var sidebarUpgradeDescription: NSTextField?
|
|
|
private weak var sidebarUpgradeButton: HoverableButton?
|
|
|
private var subscriptionObserver: NSObjectProtocol?
|
|
|
+ private var appearanceObserver: NSObjectProtocol?
|
|
|
/// Retains the system share picker until the user picks a destination or dismisses the menu.
|
|
|
private var appSharePicker: NSSharingServicePicker?
|
|
|
|
|
|
@@ -178,17 +172,37 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
override init(frame frameRect: NSRect) {
|
|
|
super.init(frame: frameRect)
|
|
|
setupLayout()
|
|
|
+ registerAppearanceObservers()
|
|
|
}
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
super.init(coder: coder)
|
|
|
setupLayout()
|
|
|
+ registerAppearanceObservers()
|
|
|
}
|
|
|
|
|
|
deinit {
|
|
|
if let subscriptionObserver {
|
|
|
NotificationCenter.default.removeObserver(subscriptionObserver)
|
|
|
}
|
|
|
+ if let appearanceObserver {
|
|
|
+ NotificationCenter.default.removeObserver(appearanceObserver)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewDidChangeEffectiveAppearance() {
|
|
|
+ super.viewDidChangeEffectiveAppearance()
|
|
|
+ applyCurrentAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func registerAppearanceObservers() {
|
|
|
+ appearanceObserver = NotificationCenter.default.addObserver(
|
|
|
+ forName: AppAppearanceManager.didChangeNotification,
|
|
|
+ object: nil,
|
|
|
+ queue: .main
|
|
|
+ ) { [weak self] _ in
|
|
|
+ self?.applyCurrentAppearance()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override func viewDidMoveToWindow() {
|
|
|
@@ -220,6 +234,95 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
savedJobOrder = Self.normalizedSavedJobs(SavedJobsStore.load())
|
|
|
resetChatState()
|
|
|
updateMainContentVisibility()
|
|
|
+ applyCurrentAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func applyCurrentAppearance() {
|
|
|
+ window?.backgroundColor = AppAppearanceManager.shared.windowChromeColor
|
|
|
+
|
|
|
+ layer?.backgroundColor = Theme.chromeBackground.cgColor
|
|
|
+ chromeContainer.layer?.backgroundColor = Theme.chromeBackground.cgColor
|
|
|
+ sidebar.layer?.backgroundColor = Theme.sidebarBackground.cgColor
|
|
|
+ mainHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ indeedJobBrowserHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ nonHomeHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ cvMakerPageContainer.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ profilePageContainer.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
+ settingsPageContainer.layer?.backgroundColor = Theme.settingsPageBackground.cgColor
|
|
|
+
|
|
|
+ greetingLabel.textColor = Theme.welcomeHeroHeadingBlue
|
|
|
+ subtitleLabel.textColor = Theme.welcomeHeroSubtitleText
|
|
|
+ welcomeHeroBackgroundView.waveTint = Theme.welcomeHeroWaveTint
|
|
|
+ welcomeLogoWell.layer?.backgroundColor = Theme.welcomeHeroIconWell.cgColor
|
|
|
+
|
|
|
+ nonHomeTitleLabel.textColor = Theme.primaryText
|
|
|
+ nonHomeSubtitleLabel.textColor = Theme.secondaryText
|
|
|
+ savedJobsPageTitleLabel.textColor = Theme.primaryText
|
|
|
+ savedJobsPageSubtitleLabel.textColor = Theme.secondaryText
|
|
|
+
|
|
|
+ let searchHovering = searchCard.isHovering
|
|
|
+ searchCard.layer?.backgroundColor = (searchHovering ? Theme.neutralHoverFill : Theme.cardBackground).cgColor
|
|
|
+ searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
|
|
|
+ jobKeywordsField.textColor = Theme.primaryText
|
|
|
+ jobKeywordsField.placeholderAttributedString = NSAttributedString(
|
|
|
+ string: "Ask for roles, skills, salary, or job descriptions...",
|
|
|
+ attributes: [
|
|
|
+ .foregroundColor: Theme.secondaryText,
|
|
|
+ .font: NSFont.systemFont(ofSize: 14, weight: .regular)
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ jobSearchIcon.contentTintColor = Theme.brandBlue
|
|
|
+ let ctaHovering = findJobsButton.isHovering
|
|
|
+ findJobsCTAPill.layer?.backgroundColor = (ctaHovering ? Theme.brandBlueHover : Theme.brandBlue).cgColor
|
|
|
+ sendIconView.contentTintColor = Theme.proCTAText
|
|
|
+ sendLabel.textColor = Theme.proCTAText
|
|
|
+
|
|
|
+ appearanceModeSegment?.selectedSegment = AppAppearanceManager.shared.mode.segmentIndex
|
|
|
+ refreshSettingsPageAppearance(in: settingsPageContainer)
|
|
|
+ rebuildFeatureShortcutCards()
|
|
|
+ configureSidebar()
|
|
|
+ reloadSavedJobsListings()
|
|
|
+ rebuildChatUI()
|
|
|
+ applyProSubscriptionToSidebar()
|
|
|
+ needsLayout = true
|
|
|
+ }
|
|
|
+
|
|
|
+ private func refreshSettingsPageAppearance(in root: NSView) {
|
|
|
+ for case let field as NSTextField in root.subviewsRecursive() where !field.isEditable {
|
|
|
+ switch field.font?.pointSize {
|
|
|
+ case 12, 14:
|
|
|
+ field.textColor = Theme.secondaryText
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for case let stack as NSStackView in root.subviewsRecursive() where stack.orientation == .vertical && stack.layer?.cornerRadius == 14 {
|
|
|
+ stack.layer?.backgroundColor = Theme.settingsGroupBackground.cgColor
|
|
|
+ stack.layer?.borderColor = Theme.border.cgColor
|
|
|
+ for case let divider as NSView in stack.arrangedSubviews where divider.bounds.height <= 1.5 && divider.wantsLayer {
|
|
|
+ divider.layer?.backgroundColor = Theme.settingsDivider.cgColor
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for case let tile as NSView in root.subviewsRecursive() where tile.layer?.cornerRadius == 9 && tile.bounds.width == 38 {
|
|
|
+ tile.layer?.backgroundColor = Theme.settingsIconBackground.cgColor
|
|
|
+ for case let icon as NSImageView in tile.subviews {
|
|
|
+ icon.contentTintColor = Theme.brandBlue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func rebuildFeatureShortcutCards() {
|
|
|
+ let selectedIndex = featureCardsRow.arrangedSubviews.firstIndex {
|
|
|
+ ($0 as? FeatureShortcutCardView)?.isSelected == true
|
|
|
+ }
|
|
|
+ featureCardsRow.arrangedSubviews.forEach {
|
|
|
+ featureCardsRow.removeArrangedSubview($0)
|
|
|
+ $0.removeFromSuperview()
|
|
|
+ }
|
|
|
+ configureFeatureShortcutCards()
|
|
|
+ if let selectedIndex, let shortcut = FeatureShortcut(rawValue: selectedIndex) {
|
|
|
+ selectFeatureShortcut(shortcut)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private func setupLayout() {
|
|
|
@@ -2039,7 +2142,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
newJobsCount: freshJobs.count,
|
|
|
isContinuation: isContinuation
|
|
|
)
|
|
|
- self.chatMessages.append(ChatMessage(role: "assistant", content: reply))
|
|
|
+ self.chatMessages.append(ChatMessage(role: "assistant", content: reply, attachedJobs: freshJobs.isEmpty ? nil : freshJobs))
|
|
|
self.appendChatBubble(text: reply, isUser: false, jobs: freshJobs)
|
|
|
case .failure(let error):
|
|
|
self.appendChatBubble(text: error.localizedDescription, isUser: false)
|
|
|
@@ -2175,13 +2278,33 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
trailingLoadMoreJobsButton = nil
|
|
|
chatMessages.removeAll()
|
|
|
lastSearchResults.removeAll()
|
|
|
+ clearChatStack()
|
|
|
+ let welcome = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary."
|
|
|
+ chatMessages.append(ChatMessage(role: "assistant", content: welcome))
|
|
|
+ appendChatBubble(text: welcome, isUser: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func clearChatStack() {
|
|
|
chatStack.arrangedSubviews.forEach {
|
|
|
chatStack.removeArrangedSubview($0)
|
|
|
$0.removeFromSuperview()
|
|
|
}
|
|
|
- let welcome = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary."
|
|
|
- chatMessages.append(ChatMessage(role: "assistant", content: welcome))
|
|
|
- appendChatBubble(text: welcome, isUser: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func rebuildChatUI() {
|
|
|
+ guard !chatMessages.isEmpty else { return }
|
|
|
+ removeInlineChatThinkingRow()
|
|
|
+ trailingLoadMoreJobsRow = nil
|
|
|
+ trailingLoadMoreJobsButton = nil
|
|
|
+ clearChatStack()
|
|
|
+ for message in chatMessages {
|
|
|
+ let isUser = message.role == "user"
|
|
|
+ appendChatBubble(text: message.content, isUser: isUser, jobs: message.attachedJobs)
|
|
|
+ }
|
|
|
+ for host in chatStack.arrangedSubviews {
|
|
|
+ host.alphaValue = 1
|
|
|
+ }
|
|
|
+ updateChatBubbleWidths()
|
|
|
}
|
|
|
|
|
|
private func appendChatBubble(text: String, isUser: Bool, jobs: [JobListing]? = nil) {
|
|
|
@@ -2741,6 +2864,31 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
private struct ChatMessage: Codable {
|
|
|
let role: String
|
|
|
let content: String
|
|
|
+ /// Job cards shown under this assistant message (not sent to the API).
|
|
|
+ var attachedJobs: [JobListing]?
|
|
|
+
|
|
|
+ enum CodingKeys: String, CodingKey {
|
|
|
+ case role
|
|
|
+ case content
|
|
|
+ }
|
|
|
+
|
|
|
+ init(role: String, content: String, attachedJobs: [JobListing]? = nil) {
|
|
|
+ self.role = role
|
|
|
+ self.content = content
|
|
|
+ self.attachedJobs = attachedJobs
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private extension NSView {
|
|
|
+ func subviewsRecursive() -> [NSView] {
|
|
|
+ var result: [NSView] = []
|
|
|
+ var stack: [NSView] = [self]
|
|
|
+ while let view = stack.popLast() {
|
|
|
+ result.append(view)
|
|
|
+ stack.append(contentsOf: view.subviews)
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private final class OpenAIJobSearchService {
|
|
|
@@ -3303,9 +3451,12 @@ private final class WelcomeHeroBackgroundView: NSView {
|
|
|
/// Home welcome row: three tappable shortcuts that seed the main search field (reference: white cards, pastel icon well, arrow at bottom trailing).
|
|
|
private final class FeatureShortcutCardView: NSView {
|
|
|
private static let cardCornerRadius: CGFloat = 14
|
|
|
- private static let primaryBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
|
|
|
- private static let defaultBorderColor = NSColor(srgbRed: 237 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1)
|
|
|
var isSelected = false { didSet { updateSelectionAppearance() } }
|
|
|
+ private weak var titleField: NSTextField?
|
|
|
+ private weak var subtitleField: NSTextField?
|
|
|
+ private weak var iconHost: NSView?
|
|
|
+ private weak var iconView: NSImageView?
|
|
|
+ private weak var chevronView: NSImageView?
|
|
|
private weak var actionTarget: AnyObject?
|
|
|
private var actionSelector: Selector
|
|
|
|
|
|
@@ -3319,37 +3470,31 @@ private final class FeatureShortcutCardView: NSView {
|
|
|
if #available(macOS 11.0, *) {
|
|
|
layer?.cornerCurve = .continuous
|
|
|
}
|
|
|
- layer?.backgroundColor = NSColor.white.cgColor
|
|
|
+ layer?.backgroundColor = AppDashboardTheme.featureCardBackground.cgColor
|
|
|
layer?.masksToBounds = false
|
|
|
- layer?.shadowColor = NSColor.black.withAlphaComponent(0.06).cgColor
|
|
|
+ layer?.shadowColor = NSColor.black.withAlphaComponent(AppDashboardTheme.isDark ? 0.35 : 0.06).cgColor
|
|
|
layer?.shadowOffset = CGSize(width: 0, height: 2)
|
|
|
layer?.shadowRadius = 12
|
|
|
layer?.shadowOpacity = 1
|
|
|
updateSelectionAppearance()
|
|
|
|
|
|
- let primaryBlue = Self.primaryBlue
|
|
|
- // `#EBF2FF` — circular icon well.
|
|
|
- let iconWellColor = NSColor(srgbRed: 235 / 255, green: 242 / 255, blue: 255 / 255, alpha: 1)
|
|
|
- // `#5D6D7E` — muted description.
|
|
|
- let secondary = NSColor(srgbRed: 93 / 255, green: 109 / 255, blue: 126 / 255, alpha: 1)
|
|
|
-
|
|
|
let iconSize: CGFloat = 40
|
|
|
let iconHost = NSView()
|
|
|
+ self.iconHost = iconHost
|
|
|
iconHost.translatesAutoresizingMaskIntoConstraints = false
|
|
|
iconHost.wantsLayer = true
|
|
|
- iconHost.layer?.backgroundColor = iconWellColor.cgColor
|
|
|
iconHost.layer?.cornerRadius = iconSize / 2
|
|
|
|
|
|
let icon = NSImageView()
|
|
|
+ self.iconView = icon
|
|
|
icon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .regular)
|
|
|
icon.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
|
|
|
- icon.contentTintColor = primaryBlue
|
|
|
iconHost.addSubview(icon)
|
|
|
|
|
|
let titleField = NSTextField(wrappingLabelWithString: title)
|
|
|
+ self.titleField = titleField
|
|
|
titleField.font = .systemFont(ofSize: 15, weight: .bold)
|
|
|
- titleField.textColor = primaryBlue
|
|
|
titleField.maximumNumberOfLines = 1
|
|
|
titleField.isEditable = false
|
|
|
titleField.isBordered = false
|
|
|
@@ -3357,8 +3502,8 @@ private final class FeatureShortcutCardView: NSView {
|
|
|
titleField.alignment = .left
|
|
|
|
|
|
let subtitleField = NSTextField(wrappingLabelWithString: subtitle)
|
|
|
+ self.subtitleField = subtitleField
|
|
|
subtitleField.font = .systemFont(ofSize: 12, weight: .regular)
|
|
|
- subtitleField.textColor = secondary
|
|
|
subtitleField.maximumNumberOfLines = 2
|
|
|
subtitleField.isEditable = false
|
|
|
subtitleField.isBordered = false
|
|
|
@@ -3369,10 +3514,10 @@ private final class FeatureShortcutCardView: NSView {
|
|
|
subtitleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
|
|
|
let chevron = NSImageView()
|
|
|
+ self.chevronView = chevron
|
|
|
chevron.translatesAutoresizingMaskIntoConstraints = false
|
|
|
chevron.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
|
|
|
chevron.image = NSImage(systemSymbolName: "arrow.right", accessibilityDescription: nil)
|
|
|
- chevron.contentTintColor = primaryBlue
|
|
|
chevron.setContentHuggingPriority(.required, for: .horizontal)
|
|
|
chevron.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
|
|
|
|
@@ -3418,17 +3563,36 @@ private final class FeatureShortcutCardView: NSView {
|
|
|
setAccessibilityElement(true)
|
|
|
setAccessibilityRole(.button)
|
|
|
setAccessibilityLabel("\(title). \(subtitle)")
|
|
|
+ applyCardAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ func applyCardAppearance() {
|
|
|
+ layer?.backgroundColor = AppDashboardTheme.featureCardBackground.cgColor
|
|
|
+ layer?.shadowColor = NSColor.black.withAlphaComponent(AppDashboardTheme.isDark ? 0.35 : 0.06).cgColor
|
|
|
+ iconHost?.layer?.backgroundColor = AppDashboardTheme.featureIconWell.cgColor
|
|
|
+ let accent = AppDashboardTheme.featurePrimaryBlue
|
|
|
+ iconView?.contentTintColor = accent
|
|
|
+ titleField?.textColor = accent
|
|
|
+ subtitleField?.textColor = AppDashboardTheme.featureSecondaryText
|
|
|
+ chevronView?.contentTintColor = accent
|
|
|
+ updateSelectionAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewDidChangeEffectiveAppearance() {
|
|
|
+ super.viewDidChangeEffectiveAppearance()
|
|
|
+ applyCardAppearance()
|
|
|
}
|
|
|
|
|
|
private func updateSelectionAppearance() {
|
|
|
guard let layer else { return }
|
|
|
+ let accent = AppDashboardTheme.featurePrimaryBlue
|
|
|
if isSelected {
|
|
|
layer.borderWidth = 2
|
|
|
- layer.borderColor = Self.primaryBlue.cgColor
|
|
|
- layer.shadowOpacity = 0.1
|
|
|
+ layer.borderColor = accent.cgColor
|
|
|
+ layer.shadowOpacity = AppDashboardTheme.isDark ? 0.2 : 0.1
|
|
|
} else {
|
|
|
layer.borderWidth = 1
|
|
|
- layer.borderColor = Self.defaultBorderColor.cgColor
|
|
|
+ layer.borderColor = AppDashboardTheme.featureCardBorder.cgColor
|
|
|
layer.shadowOpacity = 1
|
|
|
}
|
|
|
setAccessibilitySelected(isSelected)
|