Skip to content

Commit fd12cdf

Browse files
committed
Added a close button click state, allows clicking under scroll view.
1 parent 5752c3e commit fd12cdf

4 files changed

Lines changed: 104 additions & 76 deletions

File tree

CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,6 @@ struct FileInspectorView: View {
5959
widthOptions
6060
wrapLinesToggle
6161
}
62-
Section("Test Notifications") {
63-
Button("Add Test Notification") {
64-
NotificationManager.shared.post(
65-
iconSymbol: "bell.badge.fill",
66-
iconColor: .red,
67-
title: "Test Notification",
68-
description: "This is a test notification",
69-
actionButtonTitle: "Action",
70-
action: {
71-
print("Test notification action triggered")
72-
}
73-
)
74-
}
75-
Button("Add Sticky Notification") {
76-
NotificationManager.shared.post(
77-
iconSymbol: "pin.fill",
78-
iconColor: .orange,
79-
title: "Sticky Notification",
80-
description: "This notification will stay until dismissed",
81-
actionButtonTitle: "Acknowledge",
82-
action: {
83-
print("Sticky notification acknowledged")
84-
},
85-
isSticky: true
86-
)
87-
}
88-
}
8962
}
9063
} else {
9164
NoSelectionInspectorView()

CodeEdit/Features/Notifications/Views/NotificationBannerView.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@
77

88
import SwiftUI
99

10+
struct CloseButtonStyle: ButtonStyle {
11+
@Environment(\.colorScheme)
12+
private var colorScheme
13+
14+
func makeBody(configuration: Configuration) -> some View {
15+
configuration.label
16+
.font(.system(size: 10))
17+
.foregroundColor(.secondary)
18+
.frame(width: 20, height: 20, alignment: .center)
19+
.background(Color.primary.opacity(configuration.isPressed ? colorScheme == .dark ? 0.10 : 0.05 : 0.00))
20+
.background(.regularMaterial)
21+
.overlay(
22+
RoundedRectangle(cornerRadius: 10)
23+
.stroke(Color(nsColor: .separatorColor), lineWidth: 2)
24+
)
25+
.cornerRadius(10)
26+
.shadow(
27+
color: Color(.black.withAlphaComponent(colorScheme == .dark ? 0.2 : 0.1)),
28+
radius: 5,
29+
x: 0,
30+
y: 2
31+
)
32+
}
33+
}
34+
1035
struct NotificationBannerView: View {
1136
@Environment(\.colorScheme)
1237
private var colorScheme

CodeEdit/Features/Notifications/Views/NotificationOverlayView.swift

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,67 +19,100 @@ struct NotificationOverlayView: View {
1919
// Fixed width for notifications
2020
private let notificationWidth: CGFloat = 320 // 300 + 10 padding on each side
2121

22+
@State private var hasOverflow: Bool = false
23+
@State private var contentHeight: CGFloat = 0.0
24+
25+
private func updateOverflow(contentHeight: CGFloat, containerHeight: CGFloat) {
26+
if !hasOverflow && contentHeight > containerHeight {
27+
hasOverflow = true
28+
} else if hasOverflow && contentHeight <= containerHeight {
29+
hasOverflow = false
30+
}
31+
}
32+
2233
var notifications: some View {
2334
VStack(spacing: 8) {
24-
ForEach(notificationManager.activeNotifications, id: \.id) { notification in
25-
if controlActiveState == .active || controlActiveState == .key {
26-
NotificationBannerView(
27-
notification: notification,
28-
onDismiss: {
29-
notificationManager.dismissNotification(notification)
30-
},
31-
onAction: {
32-
notification.action()
33-
notificationManager.dismissNotification(notification)
34-
// Only hide if manually shown
35-
if notificationManager.isManuallyShown {
36-
notificationManager.toggleNotificationsVisibility()
37-
}
35+
ForEach(
36+
notificationManager.activeNotifications.filter {
37+
notificationManager.isNotificationVisible($0)
38+
},
39+
id: \.id
40+
) { notification in
41+
NotificationBannerView(
42+
notification: notification,
43+
onDismiss: {
44+
notificationManager.dismissNotification(notification)
45+
},
46+
onAction: {
47+
notification.action()
48+
notificationManager.dismissNotification(notification)
49+
// Only hide if manually shown
50+
if notificationManager.isManuallyShown {
51+
notificationManager.toggleNotificationsVisibility()
3852
}
39-
)
40-
.transition(.asymmetric(
41-
insertion: .move(edge: .trailing),
42-
removal: .opacity
43-
))
44-
}
53+
}
54+
)
4555
}
4656
}
4757
.padding(10)
4858
}
4959

50-
var body: some View {
51-
ViewThatFits(in: .vertical) {
52-
notifications
53-
.border(.red)
54-
GeometryReader { geometry in
55-
HStack {
56-
Spacer() // Push content to trailing edge
57-
ScrollViewReader { proxy in
58-
ScrollView(.vertical, showsIndicators: false) {
59-
VStack(spacing: 0) {
60-
// Invisible anchor view at the top to scroll back to when closed
61-
Color.clear.frame(height: 0).id(topID)
62-
notifications
63-
}
64-
.padding(.bottom, 30) // Account for the status bar
60+
var notificationsWithScrollView: some View {
61+
GeometryReader { geometry in
62+
HStack {
63+
Spacer() // Push content to trailing edge
64+
ScrollViewReader { proxy in
65+
ScrollView(.vertical, showsIndicators: false) {
66+
VStack(alignment: .trailing, spacing: 0) {
67+
// Invisible anchor view at the top to scroll back to when closed
68+
Color.clear.frame(height: 0).id(topID)
69+
notifications
6570
}
66-
.frame(width: notificationWidth)
67-
.frame(maxHeight: geometry.size.height)
68-
.scrollDisabled(!notificationManager.isManuallyShown)
69-
.onChange(of: notificationManager.isManuallyShown) { isShown in
70-
if !isShown {
71-
// Delay scroll animation until after notifications are hidden
72-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
73-
withAnimation(.easeOut(duration: 0.3)) {
74-
proxy.scrollTo(topID, anchor: .top)
75-
}
71+
.background(
72+
GeometryReader { proxy in
73+
Color.clear.onChange(of: proxy.size.height) { newValue in
74+
contentHeight = newValue
75+
updateOverflow(contentHeight: newValue, containerHeight: geometry.size.height)
76+
}
77+
}
78+
)
79+
}
80+
.frame(maxWidth: notificationWidth, alignment: .trailing)
81+
.frame(height: min(geometry.size.height, contentHeight))
82+
.scrollDisabled(!hasOverflow)
83+
.onChange(of: geometry.size.height) { newValue in
84+
updateOverflow(contentHeight: contentHeight, containerHeight: newValue)
85+
}
86+
.onChange(of: notificationManager.isManuallyShown) { isShown in
87+
if !isShown {
88+
// Delay scroll animation until after notifications are hidden
89+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
90+
withAnimation(.easeOut(duration: 0.3)) {
91+
proxy.scrollTo(topID, anchor: .top)
7692
}
7793
}
7894
}
7995
}
96+
.allowsHitTesting(
97+
notificationManager.activeNotifications
98+
.contains { notificationManager.isNotificationVisible($0) }
99+
)
80100
}
81-
.animation(.easeInOut(duration: 0.3), value: notificationManager.activeNotifications)
82101
}
83102
}
84103
}
104+
105+
var body: some View {
106+
Group {
107+
if #available(macOS 14.0, *) {
108+
notificationsWithScrollView
109+
.scrollClipDisabled(true)
110+
} else {
111+
notificationsWithScrollView
112+
}
113+
}
114+
.opacity(controlActiveState == .active || controlActiveState == .key ? 1 : 0)
115+
.offset(x: controlActiveState == .active || controlActiveState == .key ? 0 : 350)
116+
.animation(.easeInOut(duration: 0.2), value: controlActiveState)
117+
}
85118
}

CodeEdit/WorkspaceView.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ struct WorkspaceView: View {
5353
focus: $focusedEditor
5454
)
5555
.frame(maxWidth: .infinity, maxHeight: .infinity)
56-
.overlay(alignment: .topTrailing) {
57-
NotificationOverlayView()
58-
}
5956
.onChange(of: geo.size.height) { newHeight in
6057
editorsHeight = newHeight
6158
}

0 commit comments

Comments
 (0)