Skip to content

Commit aae7a56

Browse files
committed
Add buttons to toast
1 parent df4d976 commit aae7a56

File tree

3 files changed

+118
-21
lines changed

3 files changed

+118
-21
lines changed

Core/Sources/SuggestionWidget/SuggestionPanelContent/ToastPanelView.swift

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,92 @@ struct ToastPanelView: View {
1212
VStack(spacing: 4) {
1313
if !store.alignTopToAnchor {
1414
Spacer()
15+
.allowsHitTesting(false)
1516
}
1617

1718
ForEach(store.toast.messages) { message in
18-
message.content
19-
.foregroundColor(.white)
20-
.padding(8)
21-
.frame(maxWidth: .infinity)
22-
.background({
23-
switch message.type {
24-
case .info: return Color.accentColor
25-
case .error: return Color(nsColor: .systemRed)
26-
case .warning: return Color(nsColor: .systemOrange)
19+
HStack {
20+
message.content
21+
.foregroundColor(.white)
22+
.textSelection(.enabled)
23+
24+
25+
if !message.buttons.isEmpty {
26+
HStack {
27+
ForEach(
28+
Array(message.buttons.enumerated()),
29+
id: \.offset
30+
) { _, button in
31+
Button(action: button.action) {
32+
button.label
33+
.foregroundColor(.white)
34+
.padding(.horizontal, 6)
35+
.padding(.vertical, 2)
36+
.background {
37+
RoundedRectangle(cornerRadius: 4)
38+
.stroke(Color.white, lineWidth: 1)
39+
}
40+
.contentShape(Rectangle())
41+
}
42+
.buttonStyle(.plain)
43+
.allowsHitTesting(true)
44+
}
2745
}
28-
}() as Color, in: RoundedRectangle(cornerRadius: 8))
29-
.overlay {
30-
RoundedRectangle(cornerRadius: 8)
31-
.stroke(Color.black.opacity(0.1), lineWidth: 1)
3246
}
47+
}
48+
.padding(8)
49+
.frame(maxWidth: .infinity)
50+
.background({
51+
switch message.type {
52+
case .info: return Color.accentColor
53+
case .error: return Color(nsColor: .systemRed)
54+
case .warning: return Color(nsColor: .systemOrange)
55+
}
56+
}() as Color, in: RoundedRectangle(cornerRadius: 8))
57+
.overlay {
58+
RoundedRectangle(cornerRadius: 8)
59+
.stroke(Color.black.opacity(0.1), lineWidth: 1)
60+
}
3361
}
3462

3563
if store.alignTopToAnchor {
3664
Spacer()
65+
.allowsHitTesting(false)
3766
}
3867
}
3968
.colorScheme(store.colorScheme)
4069
.frame(maxWidth: .infinity, maxHeight: .infinity)
41-
.allowsHitTesting(false)
4270
}
4371
}
4472
}
4573

74+
#Preview {
75+
ToastPanelView(store: .init(initialState: .init(
76+
toast: .init(messages: [
77+
ToastController.Message(
78+
id: UUID(),
79+
type: .info,
80+
content: Text("Info message"),
81+
buttons: [
82+
.init(label: Text("Dismiss"), action: {}),
83+
.init(label: Text("More info"), action: {}),
84+
]
85+
),
86+
ToastController.Message(
87+
id: UUID(),
88+
type: .error,
89+
content: Text("Error message"),
90+
buttons: [.init(label: Text("Dismiss"), action: {})]
91+
),
92+
ToastController.Message(
93+
id: UUID(),
94+
type: .warning,
95+
content: Text("Warning message"),
96+
buttons: [.init(label: Text("Dismiss"), action: {})]
97+
),
98+
])
99+
), reducer: {
100+
ToastPanel()
101+
}))
102+
}
103+

Core/Sources/SuggestionWidget/WidgetWindowsController.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ public final class WidgetWindows {
793793
defer: false
794794
)
795795
it.isReleasedWhenClosed = false
796-
it.isOpaque = true
796+
it.isOpaque = false
797797
it.backgroundColor = .clear
798798
it.level = widgetLevel(0)
799799
it.hasShadow = false
@@ -804,7 +804,6 @@ public final class WidgetWindows {
804804
))
805805
)
806806
it.setIsVisible(true)
807-
it.ignoresMouseEvents = true
808807
it.canBecomeKeyChecker = { false }
809808
return it
810809
}()

Tool/Sources/Toast/Toast.swift

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,36 @@ public extension DependencyValues {
4646

4747
public class ToastController: ObservableObject {
4848
public struct Message: Identifiable, Equatable {
49+
public struct MessageButton: Equatable {
50+
public static func == (lhs: Self, rhs: Self) -> Bool {
51+
lhs.label == rhs.label
52+
}
53+
54+
public var label: Text
55+
public var action: () -> Void
56+
public init(label: Text, action: @escaping () -> Void) {
57+
self.label = label
58+
self.action = action
59+
}
60+
}
61+
4962
public var namespace: String?
5063
public var id: UUID
5164
public var type: ToastType
5265
public var content: Text
53-
public init(id: UUID, type: ToastType, namespace: String? = nil, content: Text) {
66+
public var buttons: [MessageButton]
67+
public init(
68+
id: UUID,
69+
type: ToastType,
70+
namespace: String? = nil,
71+
content: Text,
72+
buttons: [MessageButton] = []
73+
) {
5474
self.namespace = namespace
5575
self.id = id
5676
self.type = type
5777
self.content = content
78+
self.buttons = buttons
5879
}
5980
}
6081

@@ -64,16 +85,35 @@ public class ToastController: ObservableObject {
6485
self.messages = messages
6586
}
6687

67-
public func toast(content: String, type: ToastType, namespace: String? = nil) {
88+
public func toast(
89+
content: String,
90+
type: ToastType,
91+
namespace: String? = nil,
92+
buttons: [Message.MessageButton] = [],
93+
duration: TimeInterval = 4
94+
) {
6895
let id = UUID()
69-
let message = Message(id: id, type: type, namespace: namespace, content: Text(content))
96+
let message = Message(
97+
id: id,
98+
type: type,
99+
namespace: namespace,
100+
content: Text(content),
101+
buttons: buttons.map { b in
102+
Message.MessageButton(label: b.label, action: { [weak self] in
103+
b.action()
104+
withAnimation(.easeInOut(duration: 0.2)) {
105+
self?.messages.removeAll { $0.id == id }
106+
}
107+
})
108+
}
109+
)
70110

71111
Task { @MainActor in
72112
withAnimation(.easeInOut(duration: 0.2)) {
73113
messages.append(message)
74114
messages = messages.suffix(3)
75115
}
76-
try await Task.sleep(nanoseconds: 4_000_000_000)
116+
try await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
77117
withAnimation(.easeInOut(duration: 0.2)) {
78118
messages.removeAll { $0.id == id }
79119
}
@@ -84,7 +124,7 @@ public class ToastController: ObservableObject {
84124
@Reducer
85125
public struct Toast {
86126
public typealias Message = ToastController.Message
87-
127+
88128
@ObservableState
89129
public struct State: Equatable {
90130
var isObservingToastController = false

0 commit comments

Comments
 (0)