ソースを参照

Align CV preview export with job-card CTAs and simplify the preview.

Style Export PDF like Apply (32pt height, 8pt corners) with a bit of extra width for the label, and remove in-place editing from the filled preview and document view.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 2 ヶ月 前
コミット
8836c65012

+ 87 - 28
App for Indeed/Views/CVFilledPreviewPageView.swift

@@ -12,6 +12,85 @@ private final class CVPreviewFlippedDocumentView: NSView {
     override var isFlipped: Bool { true }
 }
 
+/// Same metrics as job cards’ **Apply** (`DashboardView` `JobPayloadButton`): 13pt semibold, 32pt tall, 8pt corners.
+private final class CVPreviewPrimaryCTAButton: NSButton {
+    private static let fill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
+    private static let fillHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
+    /// Slightly wider than default title metrics so the label is not flush to the pill edges.
+    private static let horizontalOutset: CGFloat = 20
+
+    private var trackingAreaRef: NSTrackingArea?
+    private var didPushCursor = false
+
+    init(title: String, target: Any?, action: Selector?) {
+        super.init(frame: .zero)
+        self.title = title
+        self.target = target as AnyObject?
+        self.action = action
+        translatesAutoresizingMaskIntoConstraints = false
+        isBordered = false
+        bezelStyle = .rounded
+        font = .systemFont(ofSize: 13, weight: .semibold)
+        wantsLayer = true
+        layer?.cornerRadius = 8
+        layer?.backgroundColor = Self.fill.cgColor
+        contentTintColor = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
+        focusRingType = .none
+        setContentHuggingPriority(.required, for: .horizontal)
+        setContentCompressionResistancePriority(.required, for: .horizontal)
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override var intrinsicContentSize: NSSize {
+        let s = super.intrinsicContentSize
+        guard s.width != NSView.noIntrinsicMetric, s.width >= 1 else { return s }
+        return NSSize(width: s.width + Self.horizontalOutset, height: s.height)
+    }
+
+    override func updateTrackingAreas() {
+        super.updateTrackingAreas()
+        if let ta = trackingAreaRef { removeTrackingArea(ta) }
+        let ta = NSTrackingArea(
+            rect: bounds,
+            options: [.activeInKeyWindow, .mouseEnteredAndExited, .inVisibleRect],
+            owner: self,
+            userInfo: nil
+        )
+        addTrackingArea(ta)
+        trackingAreaRef = ta
+    }
+
+    override func mouseEntered(with event: NSEvent) {
+        super.mouseEntered(with: event)
+        layer?.backgroundColor = Self.fillHover.cgColor
+        if !didPushCursor {
+            NSCursor.pointingHand.push()
+            didPushCursor = true
+        }
+    }
+
+    override func mouseExited(with event: NSEvent) {
+        super.mouseExited(with: event)
+        layer?.backgroundColor = Self.fill.cgColor
+        if didPushCursor {
+            NSCursor.pop()
+            didPushCursor = false
+        }
+    }
+
+    override func viewWillMove(toWindow newWindow: NSWindow?) {
+        super.viewWillMove(toWindow: newWindow)
+        if newWindow == nil, didPushCursor {
+            NSCursor.pop()
+            didPushCursor = false
+        }
+    }
+}
+
 /// Hosts a scrollable `CVProfileDocumentView` with a simple chrome header and back navigation.
 final class CVFilledPreviewPageView: NSView {
 
@@ -19,8 +98,7 @@ final class CVFilledPreviewPageView: NSView {
 
     private let backButton = NSButton(title: "← Profiles", target: nil, action: nil)
     private let titleLabel = NSTextField(labelWithString: "CV preview")
-    private let exportButton = NSButton(title: "Export PDF…", target: nil, action: nil)
-    private let editCheckbox = NSButton(checkboxWithTitle: "Edit text in place", target: nil, action: nil)
+    private let exportButton = CVPreviewPrimaryCTAButton(title: "Export PDF…", target: nil, action: nil)
     private let scrollView = NSScrollView()
     private let documentView = CVPreviewFlippedDocumentView()
     private let contentStack = NSStackView()
@@ -50,31 +128,15 @@ final class CVFilledPreviewPageView: NSView {
         titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
         titleLabel.textColor = NSColor(srgbRed: 31 / 255, green: 41 / 255, blue: 55 / 255, alpha: 1)
 
-        exportButton.translatesAutoresizingMaskIntoConstraints = false
-        exportButton.bezelStyle = .rounded
-        exportButton.isBordered = false
-        exportButton.contentTintColor = Self.brandBlue
-        exportButton.font = .systemFont(ofSize: 13, weight: .semibold)
         exportButton.target = self
         exportButton.action = #selector(didTapExportPDF)
 
-        editCheckbox.translatesAutoresizingMaskIntoConstraints = false
-        editCheckbox.font = .systemFont(ofSize: 12, weight: .regular)
-        editCheckbox.target = self
-        editCheckbox.action = #selector(didToggleEditMode)
-
-        let subtitle = NSTextField(wrappingLabelWithString: "Layout matches the CV Maker thumbnail for this template. Turn on editing to adjust wording, then export a PDF that matches what you see here (fonts, columns, colours, and rules).")
+        let subtitle = NSTextField(wrappingLabelWithString: "Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules).")
         subtitle.font = .systemFont(ofSize: 12, weight: .regular)
         subtitle.textColor = Self.secondaryText
         subtitle.maximumNumberOfLines = 0
 
-        let actions = NSStackView(views: [exportButton, editCheckbox])
-        actions.orientation = .horizontal
-        actions.spacing = 16
-        actions.alignment = .centerY
-        actions.translatesAutoresizingMaskIntoConstraints = false
-
-        let headerCol = NSStackView(views: [backButton, titleLabel, subtitle, actions])
+        let headerCol = NSStackView(views: [backButton, titleLabel, subtitle, exportButton])
         headerCol.orientation = .vertical
         headerCol.alignment = .leading
         headerCol.spacing = 6
@@ -106,6 +168,9 @@ final class CVFilledPreviewPageView: NSView {
             headerCol.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32),
             headerCol.topAnchor.constraint(equalTo: topAnchor, constant: 16),
 
+            exportButton.heightAnchor.constraint(equalToConstant: 32),
+            exportButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 96),
+
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
             scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
             scrollView.topAnchor.constraint(equalTo: headerCol.bottomAnchor, constant: 16),
@@ -128,15 +193,14 @@ final class CVFilledPreviewPageView: NSView {
         fatalError("init(coder:) has not been implemented")
     }
 
-    func configure(profile: SavedProfile, template: CVTemplate, isEditable: Bool = false) {
+    func configure(profile: SavedProfile, template: CVTemplate) {
         lastProfile = profile
         lastTemplate = template
-        editCheckbox.state = isEditable ? .on : .off
         for v in contentStack.arrangedSubviews {
             contentStack.removeArrangedSubview(v)
             v.removeFromSuperview()
         }
-        let doc = CVProfileDocumentView(profile: profile, template: template, isEditable: isEditable)
+        let doc = CVProfileDocumentView(profile: profile, template: template)
         profileDocumentView = doc
         contentStack.addArrangedSubview(doc)
         let profileTitle = profile.profileDisplayName.isEmpty ? "Untitled profile" : profile.profileDisplayName
@@ -147,11 +211,6 @@ final class CVFilledPreviewPageView: NSView {
         onDismiss?()
     }
 
-    @objc private func didToggleEditMode(_ sender: NSButton) {
-        guard let profile = lastProfile, let template = lastTemplate else { return }
-        configure(profile: profile, template: template, isEditable: sender.state == .on)
-    }
-
     @objc private func didTapExportPDF() {
         // NSSavePanel and sandboxed file writes must run on the main thread (and after a button
         // callback can be mis-attributed under Swift’s default actor isolation).

+ 6 - 23
App for Indeed/Views/CVProfileDocumentView.swift

@@ -181,8 +181,8 @@ private struct DocumentStyle {
 final class CVProfileDocumentView: NSView {
 
     /// Card width used in the CV preview; also the horizontal fitting size for this view.
-    /// Without this, a parent `NSStackView` that only pins `width ≤ …` sizes the document from
-    /// editable `NSTextField` intrinsic widths (~0) and the whole page collapses to a thin strip.
+    /// Without this, a parent `NSStackView` that only pins `width ≤ …` can size the document from
+    /// wrapping `NSTextField` intrinsic widths (~0) and the whole page collapses to a thin strip.
     private static let cardWidth: CGFloat = 640
 
     private let profile: SavedProfile
@@ -190,20 +190,17 @@ final class CVProfileDocumentView: NSView {
     private let style: DocumentStyle
     /// Matches `CVTemplatePreviewView` so the same template id + layout recipe renders the same silhouette as the gallery card.
     private let variant: Int
-    private let isEditable: Bool
-
-    init(profile: SavedProfile, template: CVTemplate, isEditable: Bool = false) {
+    init(profile: SavedProfile, template: CVTemplate) {
         self.profile = profile
         self.template = template
         self.style = DocumentStyle.make(for: template)
         self.variant = template.galleryLayoutVariant
-        self.isEditable = isEditable
         super.init(frame: .zero)
         translatesAutoresizingMaskIntoConstraints = false
         wantsLayer = true
         layer?.backgroundColor = NSColor.clear.cgColor
         userInterfaceLayoutDirection = .leftToRight
-        // Let the preview stack stretch us to the scroll view width; don’t shrink to editable fields.
+        // Let the preview stack stretch us to the scroll view width; don’t shrink to label intrinsic widths.
         setContentHuggingPriority(.defaultLow, for: .horizontal)
 
         let card = NSView()
@@ -245,9 +242,8 @@ final class CVProfileDocumentView: NSView {
 
     override func layout() {
         super.layout()
-        // Editable wrapping `NSTextField`s default to a very small intrinsic width until
-        // `preferredMaxLayoutWidth` tracks the column width — stacks then collapse and text
-        // reflows like a narrow strip (broken CV layout in “Edit text in place” mode).
+        // Wrapping `NSTextField`s default to a very small intrinsic width until
+        // `preferredMaxLayoutWidth` tracks the column width — stacks then collapse and text reflows like a narrow strip.
         updateWrappingTextPreferredWidths()
     }
 
@@ -1264,19 +1260,6 @@ final class CVProfileDocumentView: NSView {
         t.font = font
         t.textColor = color
         t.alignment = .left
-        if isEditable {
-            t.isEditable = true
-            t.isSelectable = true
-            t.isBordered = false
-            t.drawsBackground = false
-            t.focusRingType = .default
-            t.usesSingleLineMode = false
-            if isWrapping, let cell = t.cell as? NSTextFieldCell {
-                cell.wraps = true
-                cell.isScrollable = false
-            }
-            t.setContentHuggingPriority(.defaultLow, for: .horizontal)
-        }
         return t
     }