|
|
@@ -44,16 +44,77 @@ private enum ProfileLayoutEnforcement {
|
|
44
|
44
|
}
|
|
45
|
45
|
}
|
|
46
|
46
|
|
|
|
47
|
+/// Two fields side‑by‑side with a true 50/50 split, or stacked full‑width when compact. Avoids `NSStackView` collapsing paired columns to a narrow strip on the trailing edge.
|
|
|
48
|
+private final class ProfileDualFieldRow: NSView {
|
|
|
49
|
+ private let leftView: NSView
|
|
|
50
|
+ private let rightView: NSView
|
|
|
51
|
+ private let spacing: CGFloat
|
|
|
52
|
+ private var horizontalConstraints: [NSLayoutConstraint] = []
|
|
|
53
|
+ private var verticalConstraints: [NSLayoutConstraint] = []
|
|
|
54
|
+ private var isCompact = false
|
|
|
55
|
+
|
|
|
56
|
+ init(left: NSView, right: NSView, spacing: CGFloat = 12) {
|
|
|
57
|
+ self.leftView = left
|
|
|
58
|
+ self.rightView = right
|
|
|
59
|
+ self.spacing = spacing
|
|
|
60
|
+ super.init(frame: .zero)
|
|
|
61
|
+ translatesAutoresizingMaskIntoConstraints = false
|
|
|
62
|
+ ProfileLayoutEnforcement.applyForcedLTR(to: self)
|
|
|
63
|
+ addSubview(leftView)
|
|
|
64
|
+ addSubview(rightView)
|
|
|
65
|
+ leftView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
66
|
+ rightView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
67
|
+ leftView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
68
|
+ rightView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
69
|
+ leftView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
70
|
+ rightView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
71
|
+ horizontalConstraints = [
|
|
|
72
|
+ leftView.leftAnchor.constraint(equalTo: leftAnchor),
|
|
|
73
|
+ leftView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
74
|
+ leftView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
75
|
+ rightView.rightAnchor.constraint(equalTo: rightAnchor),
|
|
|
76
|
+ rightView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
77
|
+ rightView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
78
|
+ leftView.rightAnchor.constraint(equalTo: rightView.leftAnchor, constant: -spacing),
|
|
|
79
|
+ leftView.widthAnchor.constraint(equalTo: rightView.widthAnchor)
|
|
|
80
|
+ ]
|
|
|
81
|
+ verticalConstraints = [
|
|
|
82
|
+ leftView.leftAnchor.constraint(equalTo: leftAnchor),
|
|
|
83
|
+ leftView.rightAnchor.constraint(equalTo: rightAnchor),
|
|
|
84
|
+ leftView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
85
|
+ rightView.leftAnchor.constraint(equalTo: leftAnchor),
|
|
|
86
|
+ rightView.rightAnchor.constraint(equalTo: rightAnchor),
|
|
|
87
|
+ rightView.topAnchor.constraint(equalTo: leftView.bottomAnchor, constant: spacing),
|
|
|
88
|
+ rightView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
|
89
|
+ ]
|
|
|
90
|
+ NSLayoutConstraint.activate(horizontalConstraints)
|
|
|
91
|
+ }
|
|
|
92
|
+
|
|
|
93
|
+ required init?(coder: NSCoder) {
|
|
|
94
|
+ fatalError("init(coder:) has not been implemented")
|
|
|
95
|
+ }
|
|
|
96
|
+
|
|
|
97
|
+ func setCompact(_ compact: Bool) {
|
|
|
98
|
+ guard compact != isCompact else { return }
|
|
|
99
|
+ isCompact = compact
|
|
|
100
|
+ if compact {
|
|
|
101
|
+ NSLayoutConstraint.deactivate(horizontalConstraints)
|
|
|
102
|
+ NSLayoutConstraint.activate(verticalConstraints)
|
|
|
103
|
+ } else {
|
|
|
104
|
+ NSLayoutConstraint.deactivate(verticalConstraints)
|
|
|
105
|
+ NSLayoutConstraint.activate(horizontalConstraints)
|
|
|
106
|
+ }
|
|
|
107
|
+ }
|
|
|
108
|
+}
|
|
|
109
|
+
|
|
47
|
110
|
final class MyProfilePageView: NSView {
|
|
48
|
111
|
/// Below this form content width, two-column rows stack vertically.
|
|
49
|
112
|
private static let compactFormWidth: CGFloat = 640
|
|
50
|
|
- private static let readableFormMaxWidth: CGFloat = 880
|
|
51
|
113
|
private static let horizontalPageInset: CGFloat = 24
|
|
52
|
114
|
|
|
53
|
115
|
private let scrollView = NSScrollView()
|
|
54
|
116
|
private let documentView = NSView()
|
|
55
|
117
|
private let cardView = NSView()
|
|
56
|
|
- private var readableFormWidthConstraint: NSLayoutConstraint!
|
|
57
|
118
|
private let formStack = NSStackView()
|
|
58
|
119
|
|
|
59
|
120
|
private let profileNameField = NSTextField()
|
|
|
@@ -69,8 +130,8 @@ final class MyProfilePageView: NSView {
|
|
69
|
130
|
private let referralField = NSTextField()
|
|
70
|
131
|
private let saveButton = ProfilePrimaryButton(title: "Save Profile →", target: nil, action: nil)
|
|
71
|
132
|
|
|
72
|
|
- private let nameEmailRow = NSStackView()
|
|
73
|
|
- private let phoneJobRow = NSStackView()
|
|
|
133
|
+ private var nameEmailRow: ProfileDualFieldRow!
|
|
|
134
|
+ private var phoneJobRow: ProfileDualFieldRow!
|
|
74
|
135
|
|
|
75
|
136
|
private let workExperienceRowsStack = NSStackView()
|
|
76
|
137
|
private var workExperienceEntries: [WorkExperienceEntryView] = []
|
|
|
@@ -97,16 +158,6 @@ final class MyProfilePageView: NSView {
|
|
97
|
158
|
setup()
|
|
98
|
159
|
}
|
|
99
|
160
|
|
|
100
|
|
- override func updateConstraints() {
|
|
101
|
|
- updateReadableFormCardWidthConstraint()
|
|
102
|
|
- super.updateConstraints()
|
|
103
|
|
- }
|
|
104
|
|
-
|
|
105
|
|
- override func viewDidMoveToWindow() {
|
|
106
|
|
- super.viewDidMoveToWindow()
|
|
107
|
|
- needsUpdateConstraints = true
|
|
108
|
|
- }
|
|
109
|
|
-
|
|
110
|
161
|
override func layout() {
|
|
111
|
162
|
super.layout()
|
|
112
|
163
|
if let layer = cardView.layer, layer.shadowOpacity > 0 {
|
|
|
@@ -117,18 +168,6 @@ final class MyProfilePageView: NSView {
|
|
117
|
168
|
applyResponsiveRowsIfNeeded()
|
|
118
|
169
|
}
|
|
119
|
170
|
|
|
120
|
|
- /// Keeps the card width at `min(readable max, clip − horizontal insets)` before constraint passes. If the constant is ever wider than the clip, Auto Layout can slide the card to the trailing edge and the form looks compressed / right‑aligned.
|
|
121
|
|
- private func updateReadableFormCardWidthConstraint() {
|
|
122
|
|
- let clipW = max(bounds.width, scrollView.bounds.width, scrollView.contentView.bounds.width)
|
|
123
|
|
- guard clipW > 1 else { return }
|
|
124
|
|
- let horizontalCardInset = Self.horizontalPageInset * 2
|
|
125
|
|
- let maxUsable = clipW - horizontalCardInset
|
|
126
|
|
- let target = min(Self.readableFormMaxWidth, max(1, maxUsable))
|
|
127
|
|
- if abs(readableFormWidthConstraint.constant - target) > 0.5 {
|
|
128
|
|
- readableFormWidthConstraint.constant = target
|
|
129
|
|
- }
|
|
130
|
|
- }
|
|
131
|
|
-
|
|
132
|
171
|
/// Wrapping `NSTextField`s report a tiny intrinsic width until `preferredMaxLayoutWidth` tracks the chrome width, which otherwise collapses the stack to a narrow trailing column.
|
|
133
|
172
|
private func updateMultilinePreferredLayoutWidths() {
|
|
134
|
173
|
let horizontalInset: CGFloat = 24
|
|
|
@@ -202,9 +241,6 @@ final class MyProfilePageView: NSView {
|
|
202
|
241
|
cardView.layer?.cornerCurve = .continuous
|
|
203
|
242
|
}
|
|
204
|
243
|
|
|
205
|
|
- // Start narrow so the first constraint pass cannot exceed a small host; `updateConstraints` sets the real width.
|
|
206
|
|
- readableFormWidthConstraint = cardView.widthAnchor.constraint(equalToConstant: 200)
|
|
207
|
|
-
|
|
208
|
244
|
formStack.translatesAutoresizingMaskIntoConstraints = false
|
|
209
|
245
|
formStack.orientation = .vertical
|
|
210
|
246
|
formStack.alignment = .width
|
|
|
@@ -234,44 +270,43 @@ final class MyProfilePageView: NSView {
|
|
234
|
270
|
documentView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor),
|
|
235
|
271
|
documentView.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: Self.horizontalPageInset),
|
|
236
|
272
|
|
|
237
|
|
- cardView.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: Self.horizontalPageInset),
|
|
238
|
|
- cardView.trailingAnchor.constraint(lessThanOrEqualTo: documentView.trailingAnchor, constant: -Self.horizontalPageInset),
|
|
|
273
|
+ // Pin both edges so the card always spans the clip width minus insets; a separate width equal to a large constant can conflict with the clip and slide the card to the trailing edge.
|
|
|
274
|
+ cardView.leftAnchor.constraint(equalTo: documentView.leftAnchor, constant: Self.horizontalPageInset),
|
|
|
275
|
+ cardView.rightAnchor.constraint(equalTo: documentView.rightAnchor, constant: -Self.horizontalPageInset),
|
|
239
|
276
|
cardView.topAnchor.constraint(equalTo: documentView.topAnchor, constant: Self.horizontalPageInset),
|
|
240
|
|
- readableFormWidthConstraint,
|
|
241
|
277
|
|
|
242
|
|
- formStack.leadingAnchor.constraint(equalTo: cardView.leadingAnchor),
|
|
243
|
|
- formStack.trailingAnchor.constraint(equalTo: cardView.trailingAnchor),
|
|
|
278
|
+ formStack.leftAnchor.constraint(equalTo: cardView.leftAnchor),
|
|
|
279
|
+ formStack.rightAnchor.constraint(equalTo: cardView.rightAnchor),
|
|
244
|
280
|
formStack.widthAnchor.constraint(equalTo: cardView.widthAnchor),
|
|
245
|
281
|
formStack.topAnchor.constraint(equalTo: cardView.topAnchor),
|
|
246
|
282
|
formStack.bottomAnchor.constraint(equalTo: cardView.bottomAnchor)
|
|
247
|
283
|
])
|
|
248
|
284
|
|
|
249
|
|
- formStack.addArrangedSubview(labeledGroup(title: "Profile Name *", field: profileNameField, placeholder: "Marketing Director Profile"))
|
|
250
|
|
- formStack.addArrangedSubview(sectionHeading("Personal Information"))
|
|
|
285
|
+ addFullWidthArrangedSubview(
|
|
|
286
|
+ labeledGroup(title: "Profile Name *", field: profileNameField, placeholder: "Marketing Director Profile")
|
|
|
287
|
+ )
|
|
|
288
|
+ addFullWidthArrangedSubview(sectionHeading("Personal Information"))
|
|
251
|
289
|
|
|
252
|
290
|
let nameGroup = labeledGroup(title: "Full Name *", field: fullNameField, placeholder: "John Doe")
|
|
253
|
291
|
let emailGroup = labeledGroup(title: "Email *", field: emailField, placeholder: "john@example.com")
|
|
254
|
|
- configureTwoColumnRow(nameEmailRow, left: nameGroup, right: emailGroup)
|
|
255
|
|
- pinEqualColumnWidths(in: nameEmailRow)
|
|
256
|
|
- formStack.addArrangedSubview(nameEmailRow)
|
|
|
292
|
+ nameEmailRow = ProfileDualFieldRow(left: nameGroup, right: emailGroup, spacing: 12)
|
|
|
293
|
+ addFullWidthArrangedSubview(nameEmailRow)
|
|
257
|
294
|
|
|
258
|
295
|
let phoneGroup = labeledGroup(title: "Phone", field: phoneField, placeholder: "+1 (555) 123-4567")
|
|
259
|
296
|
let jobGroup = labeledGroup(title: "Job Title *", field: jobTitleField, placeholder: "Software Engineer")
|
|
260
|
|
- configureTwoColumnRow(phoneJobRow, left: phoneGroup, right: jobGroup)
|
|
261
|
|
- pinEqualColumnWidths(in: phoneJobRow)
|
|
262
|
|
- formStack.addArrangedSubview(phoneJobRow)
|
|
263
|
|
-
|
|
264
|
|
- ProfileLayoutEnforcement.applyForcedLTR(to: nameEmailRow)
|
|
265
|
|
- ProfileLayoutEnforcement.applyForcedLTR(to: phoneJobRow)
|
|
266
|
|
-
|
|
267
|
|
- formStack.addArrangedSubview(labeledGroup(title: "Address", field: addressField, placeholder: "123 Main St, City, State, ZIP"))
|
|
268
|
|
- formStack.addArrangedSubview(careerSummaryBlock())
|
|
269
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
270
|
|
- formStack.addArrangedSubview(workExperienceSection())
|
|
271
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
272
|
|
- formStack.addArrangedSubview(educationSection())
|
|
273
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
274
|
|
- formStack.addArrangedSubview(
|
|
|
297
|
+ phoneJobRow = ProfileDualFieldRow(left: phoneGroup, right: jobGroup, spacing: 12)
|
|
|
298
|
+ addFullWidthArrangedSubview(phoneJobRow)
|
|
|
299
|
+
|
|
|
300
|
+ addFullWidthArrangedSubview(
|
|
|
301
|
+ labeledGroup(title: "Address", field: addressField, placeholder: "123 Main St, City, State, ZIP")
|
|
|
302
|
+ )
|
|
|
303
|
+ addFullWidthArrangedSubview(careerSummaryBlock())
|
|
|
304
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
305
|
+ addFullWidthArrangedSubview(workExperienceSection())
|
|
|
306
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
307
|
+ addFullWidthArrangedSubview(educationSection())
|
|
|
308
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
309
|
+ addFullWidthArrangedSubview(
|
|
275
|
310
|
multilineProfileBlock(
|
|
276
|
311
|
title: "Certificates / Rewards",
|
|
277
|
312
|
placeholder: "List your certificates and awards...",
|
|
|
@@ -279,8 +314,8 @@ final class MyProfilePageView: NSView {
|
|
279
|
314
|
minHeight: 100
|
|
280
|
315
|
)
|
|
281
|
316
|
)
|
|
282
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
283
|
|
- formStack.addArrangedSubview(
|
|
|
317
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
318
|
+ addFullWidthArrangedSubview(
|
|
284
|
319
|
multilineProfileBlock(
|
|
285
|
320
|
title: "Interests",
|
|
286
|
321
|
placeholder: "List your interests and hobbies...",
|
|
|
@@ -288,8 +323,8 @@ final class MyProfilePageView: NSView {
|
|
288
|
323
|
minHeight: 100
|
|
289
|
324
|
)
|
|
290
|
325
|
)
|
|
291
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
292
|
|
- formStack.addArrangedSubview(
|
|
|
326
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
327
|
+ addFullWidthArrangedSubview(
|
|
293
|
328
|
multilineProfileBlock(
|
|
294
|
329
|
title: "Languages",
|
|
295
|
330
|
placeholder: "List languages you speak (e.g., English - Native, Spanish - Fluent)...",
|
|
|
@@ -297,9 +332,9 @@ final class MyProfilePageView: NSView {
|
|
297
|
332
|
minHeight: 100
|
|
298
|
333
|
)
|
|
299
|
334
|
)
|
|
300
|
|
- formStack.addArrangedSubview(horizontalSeparator())
|
|
301
|
|
- formStack.addArrangedSubview(referralBlock())
|
|
302
|
|
- formStack.addArrangedSubview(saveButtonHost())
|
|
|
335
|
+ addFullWidthArrangedSubview(horizontalSeparator())
|
|
|
336
|
+ addFullWidthArrangedSubview(referralBlock())
|
|
|
337
|
+ addFullWidthArrangedSubview(saveButtonHost())
|
|
303
|
338
|
|
|
304
|
339
|
saveButton.target = self
|
|
305
|
340
|
saveButton.action = #selector(didTapSave)
|
|
|
@@ -315,14 +350,8 @@ final class MyProfilePageView: NSView {
|
|
315
|
350
|
let compact = formWidth < Self.compactFormWidth
|
|
316
|
351
|
guard compact != lastCompactLayout else { return }
|
|
317
|
352
|
lastCompactLayout = compact
|
|
318
|
|
- let orientation: NSUserInterfaceLayoutOrientation = compact ? .vertical : .horizontal
|
|
319
|
|
- let rowSpacing: CGFloat = compact ? 16 : 12
|
|
320
|
|
- nameEmailRow.orientation = orientation
|
|
321
|
|
- nameEmailRow.spacing = rowSpacing
|
|
322
|
|
- phoneJobRow.orientation = orientation
|
|
323
|
|
- phoneJobRow.spacing = rowSpacing
|
|
324
|
|
- nameEmailRow.distribution = compact ? .fill : .fillEqually
|
|
325
|
|
- phoneJobRow.distribution = compact ? .fill : .fillEqually
|
|
|
353
|
+ nameEmailRow.setCompact(compact)
|
|
|
354
|
+ phoneJobRow.setCompact(compact)
|
|
326
|
355
|
for entry in workExperienceEntries {
|
|
327
|
356
|
entry.applyCompactLayout(compact)
|
|
328
|
357
|
}
|
|
|
@@ -331,26 +360,10 @@ final class MyProfilePageView: NSView {
|
|
331
|
360
|
}
|
|
332
|
361
|
}
|
|
333
|
362
|
|
|
334
|
|
- /// Keeps two columns the same width when the row is horizontal; avoids NSTextField intrinsic width fighting the stack.
|
|
335
|
|
- private func pinEqualColumnWidths(in row: NSStackView) {
|
|
336
|
|
- guard row.arrangedSubviews.count == 2 else { return }
|
|
337
|
|
- let left = row.arrangedSubviews[0]
|
|
338
|
|
- let right = row.arrangedSubviews[1]
|
|
339
|
|
- left.widthAnchor.constraint(equalTo: right.widthAnchor).isActive = true
|
|
340
|
|
- }
|
|
341
|
|
-
|
|
342
|
|
- private func configureTwoColumnRow(_ row: NSStackView, left: NSView, right: NSView) {
|
|
343
|
|
- row.translatesAutoresizingMaskIntoConstraints = false
|
|
344
|
|
- row.orientation = .horizontal
|
|
345
|
|
- row.spacing = 12
|
|
346
|
|
- row.distribution = .fillEqually
|
|
347
|
|
- row.alignment = .top
|
|
348
|
|
- row.userInterfaceLayoutDirection = .leftToRight
|
|
349
|
|
- ProfileLayoutEnforcement.applyForcedLTR(to: row)
|
|
350
|
|
- row.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
351
|
|
- row.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
352
|
|
- row.addArrangedSubview(left)
|
|
353
|
|
- row.addArrangedSubview(right)
|
|
|
363
|
+ private func addFullWidthArrangedSubview(_ view: NSView) {
|
|
|
364
|
+ formStack.addArrangedSubview(view)
|
|
|
365
|
+ view.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
366
|
+ view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
354
|
367
|
}
|
|
355
|
368
|
|
|
356
|
369
|
private func sectionHeading(_ text: String) -> NSView {
|
|
|
@@ -374,6 +387,9 @@ final class MyProfilePageView: NSView {
|
|
374
|
387
|
row.userInterfaceLayoutDirection = .leftToRight
|
|
375
|
388
|
ProfileLayoutEnforcement.applyForcedLTR(to: row)
|
|
376
|
389
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
390
|
+ NSLayoutConstraint.activate([
|
|
|
391
|
+ label.leftAnchor.constraint(equalTo: row.leftAnchor)
|
|
|
392
|
+ ])
|
|
377
|
393
|
return row
|
|
378
|
394
|
}
|
|
379
|
395
|
|
|
|
@@ -400,7 +416,8 @@ final class MyProfilePageView: NSView {
|
|
400
|
416
|
wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
401
|
417
|
wrap.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
402
|
418
|
NSLayoutConstraint.activate([
|
|
403
|
|
- wrap.widthAnchor.constraint(equalTo: stack.widthAnchor)
|
|
|
419
|
+ wrap.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
|
|
420
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
404
|
421
|
])
|
|
405
|
422
|
return stack
|
|
406
|
423
|
}
|
|
|
@@ -512,6 +529,9 @@ final class MyProfilePageView: NSView {
|
|
512
|
529
|
ProfileLayoutEnforcement.applyForcedLTR(to: stack)
|
|
513
|
530
|
stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
514
|
531
|
wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
532
|
+ NSLayoutConstraint.activate([
|
|
|
533
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
|
534
|
+ ])
|
|
515
|
535
|
return stack
|
|
516
|
536
|
}
|
|
517
|
537
|
|
|
|
@@ -576,6 +596,9 @@ final class MyProfilePageView: NSView {
|
|
576
|
596
|
ProfileLayoutEnforcement.applyForcedLTR(to: stack)
|
|
577
|
597
|
stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
578
|
598
|
wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
599
|
+ NSLayoutConstraint.activate([
|
|
|
600
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
|
601
|
+ ])
|
|
579
|
602
|
return stack
|
|
580
|
603
|
}
|
|
581
|
604
|
|
|
|
@@ -608,6 +631,10 @@ final class MyProfilePageView: NSView {
|
|
608
|
631
|
if #available(macOS 10.11, *) {
|
|
609
|
632
|
stack.setCustomSpacing(6, after: wrap)
|
|
610
|
633
|
}
|
|
|
634
|
+ NSLayoutConstraint.activate([
|
|
|
635
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor),
|
|
|
636
|
+ helper.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
|
637
|
+ ])
|
|
611
|
638
|
return stack
|
|
612
|
639
|
}
|
|
613
|
640
|
|
|
|
@@ -826,7 +853,7 @@ private final class WorkExperienceEntryView: NSView {
|
|
826
|
853
|
private let companyField = NSTextField()
|
|
827
|
854
|
private let durationField = NSTextField()
|
|
828
|
855
|
private let descriptionField = NSTextField()
|
|
829
|
|
- private let jobCompanyRow = NSStackView()
|
|
|
856
|
+ private var jobCompanyRow: ProfileDualFieldRow!
|
|
830
|
857
|
|
|
831
|
858
|
override init(frame frameRect: NSRect) {
|
|
832
|
859
|
super.init(frame: frameRect)
|
|
|
@@ -846,9 +873,7 @@ private final class WorkExperienceEntryView: NSView {
|
|
846
|
873
|
}
|
|
847
|
874
|
|
|
848
|
875
|
func applyCompactLayout(_ compact: Bool) {
|
|
849
|
|
- jobCompanyRow.orientation = compact ? .vertical : .horizontal
|
|
850
|
|
- jobCompanyRow.spacing = compact ? 12 : 12
|
|
851
|
|
- jobCompanyRow.distribution = compact ? .fill : .fillEqually
|
|
|
876
|
+ jobCompanyRow.setCompact(compact)
|
|
852
|
877
|
}
|
|
853
|
878
|
|
|
854
|
879
|
private func configure() {
|
|
|
@@ -902,8 +927,7 @@ private final class WorkExperienceEntryView: NSView {
|
|
902
|
927
|
|
|
903
|
928
|
let jobGroup = Self.labeledFieldStack(title: "Job Title *", field: jobTitleField, placeholder: "e.g., Software Engineer")
|
|
904
|
929
|
let companyGroup = Self.labeledFieldStack(title: "Company Name *", field: companyField, placeholder: "e.g., Google")
|
|
905
|
|
- configureTwoColumnRow(jobCompanyRow, left: jobGroup, right: companyGroup)
|
|
906
|
|
- pinEqualColumnWidths(in: jobCompanyRow)
|
|
|
930
|
+ jobCompanyRow = ProfileDualFieldRow(left: jobGroup, right: companyGroup, spacing: 12)
|
|
907
|
931
|
|
|
908
|
932
|
let durationGroup = Self.labeledFieldStack(title: "Duration *", field: durationField, placeholder: "e.g., Jan 2020 - Present")
|
|
909
|
933
|
let descriptionGroup = Self.multilineLabeledStack(
|
|
|
@@ -931,20 +955,6 @@ private final class WorkExperienceEntryView: NSView {
|
|
931
|
955
|
])
|
|
932
|
956
|
}
|
|
933
|
957
|
|
|
934
|
|
- private func configureTwoColumnRow(_ row: NSStackView, left: NSView, right: NSView) {
|
|
935
|
|
- row.translatesAutoresizingMaskIntoConstraints = false
|
|
936
|
|
- row.orientation = .horizontal
|
|
937
|
|
- row.spacing = 12
|
|
938
|
|
- row.distribution = .fillEqually
|
|
939
|
|
- row.alignment = .top
|
|
940
|
|
- row.userInterfaceLayoutDirection = .leftToRight
|
|
941
|
|
- ProfileLayoutEnforcement.applyForcedLTR(to: row)
|
|
942
|
|
- row.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
943
|
|
- row.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
944
|
|
- row.addArrangedSubview(left)
|
|
945
|
|
- row.addArrangedSubview(right)
|
|
946
|
|
- }
|
|
947
|
|
-
|
|
948
|
958
|
override func layout() {
|
|
949
|
959
|
super.layout()
|
|
950
|
960
|
for field in [jobTitleField, companyField, durationField, descriptionField] {
|
|
|
@@ -960,13 +970,6 @@ private final class WorkExperienceEntryView: NSView {
|
|
960
|
970
|
layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: r, cornerHeight: r, transform: nil)
|
|
961
|
971
|
}
|
|
962
|
972
|
|
|
963
|
|
- private func pinEqualColumnWidths(in row: NSStackView) {
|
|
964
|
|
- guard row.arrangedSubviews.count == 2 else { return }
|
|
965
|
|
- let left = row.arrangedSubviews[0]
|
|
966
|
|
- let right = row.arrangedSubviews[1]
|
|
967
|
|
- left.widthAnchor.constraint(equalTo: right.widthAnchor).isActive = true
|
|
968
|
|
- }
|
|
969
|
|
-
|
|
970
|
973
|
@objc private func didTapDelete() {
|
|
971
|
974
|
onDelete?()
|
|
972
|
975
|
}
|
|
|
@@ -988,7 +991,13 @@ private final class WorkExperienceEntryView: NSView {
|
|
988
|
991
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
989
|
992
|
stack.userInterfaceLayoutDirection = .leftToRight
|
|
990
|
993
|
ProfileLayoutEnforcement.applyForcedLTR(to: stack)
|
|
991
|
|
- NSLayoutConstraint.activate([wrap.widthAnchor.constraint(equalTo: stack.widthAnchor)])
|
|
|
994
|
+ stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
995
|
+ wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
996
|
+ wrap.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
997
|
+ NSLayoutConstraint.activate([
|
|
|
998
|
+ wrap.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
|
|
999
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
|
1000
|
+ ])
|
|
992
|
1001
|
return stack
|
|
993
|
1002
|
}
|
|
994
|
1003
|
|
|
|
@@ -1048,6 +1057,11 @@ private final class WorkExperienceEntryView: NSView {
|
|
1048
|
1057
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
1049
|
1058
|
stack.userInterfaceLayoutDirection = .leftToRight
|
|
1050
|
1059
|
ProfileLayoutEnforcement.applyForcedLTR(to: stack)
|
|
|
1060
|
+ stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
1061
|
+ wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
1062
|
+ NSLayoutConstraint.activate([
|
|
|
1063
|
+ label.leftAnchor.constraint(equalTo: stack.leftAnchor)
|
|
|
1064
|
+ ])
|
|
1051
|
1065
|
return stack
|
|
1052
|
1066
|
}
|
|
1053
|
1067
|
|
|
|
@@ -1103,7 +1117,7 @@ private final class EducationEntryView: NSView {
|
|
1103
|
1117
|
private let degreeField = NSTextField()
|
|
1104
|
1118
|
private let institutionField = NSTextField()
|
|
1105
|
1119
|
private let yearField = NSTextField()
|
|
1106
|
|
- private let degreeInstitutionRow = NSStackView()
|
|
|
1120
|
+ private var degreeInstitutionRow: ProfileDualFieldRow!
|
|
1107
|
1121
|
|
|
1108
|
1122
|
override init(frame frameRect: NSRect) {
|
|
1109
|
1123
|
super.init(frame: frameRect)
|
|
|
@@ -1123,9 +1137,7 @@ private final class EducationEntryView: NSView {
|
|
1123
|
1137
|
}
|
|
1124
|
1138
|
|
|
1125
|
1139
|
func applyCompactLayout(_ compact: Bool) {
|
|
1126
|
|
- degreeInstitutionRow.orientation = compact ? .vertical : .horizontal
|
|
1127
|
|
- degreeInstitutionRow.spacing = 12
|
|
1128
|
|
- degreeInstitutionRow.distribution = compact ? .fill : .fillEqually
|
|
|
1140
|
+ degreeInstitutionRow.setCompact(compact)
|
|
1129
|
1141
|
}
|
|
1130
|
1142
|
|
|
1131
|
1143
|
private func configure() {
|
|
|
@@ -1187,8 +1199,7 @@ private final class EducationEntryView: NSView {
|
|
1187
|
1199
|
field: institutionField,
|
|
1188
|
1200
|
placeholder: "e.g., MIT"
|
|
1189
|
1201
|
)
|
|
1190
|
|
- configureTwoColumnRow(degreeInstitutionRow, left: degreeGroup, right: institutionGroup)
|
|
1191
|
|
- pinEqualColumnWidths(in: degreeInstitutionRow)
|
|
|
1202
|
+ degreeInstitutionRow = ProfileDualFieldRow(left: degreeGroup, right: institutionGroup, spacing: 12)
|
|
1192
|
1203
|
|
|
1193
|
1204
|
let yearGroup = WorkExperienceEntryView.labeledFieldStack(
|
|
1194
|
1205
|
title: "Year *",
|
|
|
@@ -1214,34 +1225,21 @@ private final class EducationEntryView: NSView {
|
|
1214
|
1225
|
])
|
|
1215
|
1226
|
}
|
|
1216
|
1227
|
|
|
1217
|
|
- private func configureTwoColumnRow(_ row: NSStackView, left: NSView, right: NSView) {
|
|
1218
|
|
- row.translatesAutoresizingMaskIntoConstraints = false
|
|
1219
|
|
- row.orientation = .horizontal
|
|
1220
|
|
- row.spacing = 12
|
|
1221
|
|
- row.distribution = .fillEqually
|
|
1222
|
|
- row.alignment = .top
|
|
1223
|
|
- row.userInterfaceLayoutDirection = .leftToRight
|
|
1224
|
|
- ProfileLayoutEnforcement.applyForcedLTR(to: row)
|
|
1225
|
|
- row.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1226
|
|
- row.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
1227
|
|
- row.addArrangedSubview(left)
|
|
1228
|
|
- row.addArrangedSubview(right)
|
|
1229
|
|
- }
|
|
1230
|
|
-
|
|
1231
|
1228
|
override func layout() {
|
|
1232
|
1229
|
super.layout()
|
|
|
1230
|
+ for field in [degreeField, institutionField, yearField] {
|
|
|
1231
|
+ if let wrap = field.superview, wrap.bounds.width > 2 {
|
|
|
1232
|
+ let w = max(1, wrap.bounds.width - 24)
|
|
|
1233
|
+ if abs(field.preferredMaxLayoutWidth - w) > 0.5 {
|
|
|
1234
|
+ field.preferredMaxLayoutWidth = w
|
|
|
1235
|
+ }
|
|
|
1236
|
+ }
|
|
|
1237
|
+ }
|
|
1233
|
1238
|
guard let layer = layer, layer.shadowOpacity > 0 else { return }
|
|
1234
|
1239
|
let r = layer.cornerRadius
|
|
1235
|
1240
|
layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: r, cornerHeight: r, transform: nil)
|
|
1236
|
1241
|
}
|
|
1237
|
1242
|
|
|
1238
|
|
- private func pinEqualColumnWidths(in row: NSStackView) {
|
|
1239
|
|
- guard row.arrangedSubviews.count == 2 else { return }
|
|
1240
|
|
- let left = row.arrangedSubviews[0]
|
|
1241
|
|
- let right = row.arrangedSubviews[1]
|
|
1242
|
|
- left.widthAnchor.constraint(equalTo: right.widthAnchor).isActive = true
|
|
1243
|
|
- }
|
|
1244
|
|
-
|
|
1245
|
1243
|
@objc private func didTapDelete() {
|
|
1246
|
1244
|
onDelete?()
|
|
1247
|
1245
|
}
|