├── .swiftpm
    └── xcode
    │   ├── package.xcworkspace
    │       ├── contents.xcworkspacedata
    │       └── xcuserdata
    │       │   └── macintoshi.xcuserdatad
    │       │       └── UserInterfaceState.xcuserstate
    │   └── xcuserdata
    │       └── macintoshi.xcuserdatad
    │           └── xcschemes
    │               └── xcschememanagement.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
    └── ToastKit
    │   ├── Enums
    │       └── ToastEnums.swift
    │   ├── Extensions
    │       └── Extension+View.swift
    │   ├── ToastKit.swift
    │   ├── ToastModifier.swift
    │   └── ToastStack
    │       ├── ToastItemModel.swift
    │       ├── ToastStackManager.swift
    │       └── ToastStackView.swift
└── Tests
    └── ToastKitTests
        ├── EnumsTests.swift
        └── ToastStackTests.swift
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | 
2 | 
4 |    
6 |    
7 | 
8 | 
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcuserdata/macintoshi.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Desp0o/ToastKit/27b97664873141e04766bb0e527d76e0f12bc0d6/.swiftpm/xcode/package.xcworkspace/xcuserdata/macintoshi.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcuserdata/macintoshi.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	SchemeUserState
 6 | 	
 7 | 		ToastKit.xcscheme_^#shared#^_
 8 | 		
 9 | 			orderHint
10 | 			0
11 | 		
12 | 	
13 | 	SuppressBuildableAutocreation
14 | 	
15 | 		ToastKit
16 | 		
17 | 			primary
18 | 			
19 | 		
20 | 		ToastKitTests
21 | 		
22 | 			primary
23 | 			
24 | 		
25 | 	
26 | 
27 | 
28 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2025 Tornike Despotashvili
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
 1 | // swift-tools-version: 6.0
 2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
 3 | 
 4 | import PackageDescription
 5 | 
 6 | let package = Package(
 7 |     name: "ToastKit",
 8 |     products: [
 9 |         // Products define the executables and libraries a package produces, making them visible to other packages.
10 |         .library(
11 |             name: "ToastKit",
12 |             targets: ["ToastKit"]),
13 |     ],
14 |     targets: [
15 |         // Targets are the basic building blocks of a package, defining a module or a test suite.
16 |         // Targets can depend on other targets in this package and products from dependencies.
17 |         .target(
18 |             name: "ToastKit"),
19 |         .testTarget(
20 |             name: "ToastKitTests",
21 |             dependencies: ["ToastKit"]
22 |         ),
23 |     ]
24 | )
25 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 | # ToastKit
  5 | ToastKit is a lightweight and fully customizable Swift package that helps you display informative toast messages in your app. It’s easy to use, supports 
  6 | various built-in toast styles like success, warning, info, error, with icons.... and also allows full customization for your specific needs.
  7 | 
  8 | You can quickly use ready-made toasts or create your own custom toast view with complete control over layout, colors, animations, icons, and more.
  9 | 
 10 | 
 11 |     
 12 | 
 13 | 
 14 | 
 15 | ## Features 🚀
 16 | - Full Customization
 17 | - Glass Effect
 18 | - Max Width Support 
 19 | - Custom Icons & SF Symbols 
 20 | - Auto Dismiss 
 21 | - Transition Types 
 22 | - Flexible Layout Direction
 23 | - Text Styling Options 
 24 | - Shadow Customization 
 25 | - Corner Radius Control 
 26 | - Optional Subtitle 
 27 | - Adaptive Stack Alignment 
 28 | - Smooth Animations 
 29 | - Manual Close Button 
 30 | - Responsive Design 
 31 | 
 32 | ---------
 33 | ### GlassEffect
 34 | 
 35 | 
 36 | 
 37 | ```swift
 38 |     VStack {
 39 |       
 40 |     }
 41 |     .frame(maxWidth: .infinity, maxHeight: .infinity)
 42 |     //simple usage
 43 |     .glassToast(isVisible: $isVisible, title: title)
 44 |     
 45 |     //with full parameters
 46 |     .glassToast(
 47 |       isVisible: $isVisible2,
 48 |       title: title,
 49 |       subtitle: "if you have iOS 26 you can use this toast",
 50 |       glassColor: .red.opacity(0.5),
 51 |       titleFontColor: .black,
 52 |       subtitleFontColor: .black,
 53 |       maxWidth: false,
 54 |       transitionType: .custom(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)).combined(with: .opacity)),
 55 |       animation: .smooth,
 56 |       vDirection: .bottom
 57 |     )
 58 | ```
 59 | 
 60 | ##### Configuration ⚙️
 61 | | Parameter          | Type                         | Default Value                  | Description |
 62 | |-------------------|------------------------------|--------------------------------|-------------|
 63 | | `isVisible`        | Binding             | —                              | Binding to control visibility. |
 64 | | `title`            | String                     | —                              | The main message displayed in the toast. |
 65 | | `subtitle`             | String                      | `""`                             | Subtitle text for additional info. |
 66 | | `titleFontColor`       | Color                       | `.white`                         | Font color of the title. |
 67 | | `subtitleFontColor`    | Color                       | `.white`                         | Font color of the subtitle. |
 68 | | `transitionType`   | ToastTransitionType         | .move(edge: .top)            | The transition animation for how the toast appears/disappears. |
 69 | | `animation`        | Animation                | .snappy                     | Animation used to present and dismiss the toast. |
 70 | | `vDirection`       | VerticalDirection           | .top                        | Vertical position of the toast (`.top` or `.bottom`). |
 71 | | `maxWidth`         | Bool                       | false                      | If `true`, toast takes maximum available width. |
 72 | 
 73 | ### Success / Warning / Error/ Info - Toasts
 74 | 
 75 | 
 76 | ##### simple toast
 77 | ```swift
 78 |     VStack {
 79 |       
 80 |     }
 81 |     .frame(maxWidth: .infinity, maxHeight: .infinity)
 82 |     .successToast(isVisible: $isVisible, title: "Success")
 83 |     .warningToast(isVisible: $isVisible, title: "Warning")
 84 |     .errorToast(isVisible: $isVisible, title: "Error")
 85 |     .infoToast(isVisible: $isVisible, title: "Info")
 86 | ```
 87 | 
 88 | ##### with full parameters
 89 | ```swift
 90 |     VStack {
 91 |       
 92 |     }
 93 |     .successToast(isVisible: $isVisible, title: "success full width", toastColor: .success, animation: .snappy, titleFontColor: .white, maxWidth: false)
 94 |     .warningToast(isVisible: $isVisible, title: "warning full width", toastColor: .warning, animation: .snappy, titleFontColor: .white, maxWidth: false)
 95 |     .errorToast(isVisible: $isVisible, title: "error full width", toastColor: .error, animation: .snappy, titleFontColor: .white, maxWidth: false)
 96 |     .infoToast(isVisible: $isVisible, title: "info full width", toastColor: .info, animation: .snappy, titleFontColor: .white, maxWidth: false)
 97 | ```
 98 | 
 99 | ##### Configuration ⚙️
100 | | Parameter               | Type                   | Default Value           | Description                         |
101 | | :----------------------| :--------------------- | :---------------------- | :---------------------------------- |
102 | | `isVisible `           | Binding          | —                       | Binding to control visibility.      |
103 | | `title`                | String                 | —                       | The main message displayed in the toast. |
104 | | `toastColor`           | ToastColorTypes        | .success / .warning / .error / .info                | The visual style or color theme of the toast .            |
105 | | `animation`            | Animation              | .snappy                 | Animation used to present and dismiss the toast.                     |
106 | | `titleFontColor`       | Color                  | .white                  | The color of the toast message text.          |
107 | | `maxWidth`             | Bool                   | false                   | Whether the toast should stretch to the maximum width.             |
108 | 
109 | ---------
110 | ### Bottom - Toasts
111 | 
112 | 
113 | ##### simple toast
114 | ```swift
115 |  VStack {
116 |       
117 |     }
118 |     .bottomToast(isVisible: $isVisible, title: "bottom")
119 | ```
120 | 
121 | ##### with full parameters
122 | ```swift
123 |  VStack {
124 |       
125 |     }
126 |     .bottomToast(
127 |       isVisible: $isVisible,
128 |       title: "bottom",
129 |       toastColor: .custom(.indigo),
130 |       animation: .bouncy,
131 |       titleFontColor: .white,
132 |       maxWidth: false
133 |     )
134 | ```
135 | 
136 | ##### Configuration ⚙️
137 | 
138 | | Parameter         | Type              | Default Value | Description |
139 | |------------------|-------------------|----------------|-------------|
140 | | `isVisible`       | Binding   | —              | Binding to control visibility. |
141 | | `title`           | String          | —              | The main message displayed in the toast. |
142 | | `toastColor`      | ToastColorTypes | .success     | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
143 | | `animation`       | Animation       | .snappy      | Animation used to present and dismiss the toast. |
144 | | `titleFontColor`  | Color           | .white       | The color of the toast message text. |
145 | | `maxWidth`        | Bool            | false        | If true, toast stretches to maximum available width. |
146 | ----------------
147 | 
148 | 
149 | ### Edge Slide Toast - Toasts
150 | 
151 | 
152 | ##### simple toast
153 | ```swift
154 |  VStack {
155 |       
156 |     }
157 |     .edgeSlideToast(isVisible: $isVisible, title: "slide")
158 | ```
159 | ##### with full parameters
160 | ```swift
161 |  VStack {
162 |       
163 |     }
164 |     .edgeSlideToast(
165 |       isVisible: $isVisible,
166 |       title: "slide",
167 |       toastColor: .info,
168 |       animation: .bouncy,
169 |       hDirection: .leading,
170 |       vDirection: .top,
171 |       titleFontColor: .white,
172 |       maxWidth: false
173 |     )
174 | ```
175 | ##### Configuration ⚙️
176 | 
177 | | Parameter         | Type                  | Default Value | Description |
178 | |------------------|-----------------------|----------------|-------------|
179 | | `isVisible`       | Binding        | —              | Binding to control visibility. |
180 | | `title`           | String               | —              | The main message displayed in the toast. |
181 | | `toastColor`      | ToastColorTypes      | .success     | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
182 | | `animation`       | Animation            | .snappy      | Animation used to present and dismiss the toast. |
183 | | `hDirection`      | HorizontalDirection  | .trailing    | Slide from horizontal edge (`.leading` or `.trailing`). |
184 | | `vDirection`      | VerticalDirection    | .top         | Vertical position of the toast (`.top` or `.bottom`). |
185 | | `titleFontColor`  | Color                | .white       | The color of the toast message text. |
186 | | `maxWidth`        | Bool                 | false        | If `true`, toast stretches to maximum available width. |
187 | ----------------
188 | 
189 | ### Toast with SF Symbol
190 | 
191 | 
192 | ##### simple toast
193 | ```swift
194 |  VStack {
195 |       
196 |     }
197 |     .toastWithSFSymbol(
198 |       isVisible: $isVisivble,
199 |       title: "Toast with SF symbol",
200 |       sfSymbolName: "sun.max"
201 |     )
202 | ```
203 | ##### with full parameters
204 | ```swift
205 |  VStack {
206 |       
207 |     }
208 |     .toastWithSFSymbol(
209 |       isVisible: $isVisivble,
210 |       title: "Toast with SF symbol",
211 |       toastColor: .custom(.indigo),
212 |       titleFontColor: .white,
213 |       sfSymbolName: "sun.max",
214 |       sfSymbolSize: 18,
215 |       sfSymbolColor: .black,
216 |       transitionType: .scale,
217 |       animation: .smooth,
218 |       vDirection: .top,
219 |       maxWidth: false,
220 |       layoutDirection: .leftToRight
221 |     )
222 | ```
223 | ##### Configuration ⚙️
224 | 
225 | | Parameter          | Type                         | Default Value                  | Description |
226 | |-------------------|------------------------------|--------------------------------|-------------|
227 | | `isVisible`        | Binding               | —                              | Binding to control visibility. |
228 | | `title`            | String                      | —                              | The main message displayed in the toast. |
229 | | `toastColor`       | ToastColorTypes             | .success                     | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
230 | | `titleFontColor`   | Color                       | .white                       | The color of the toast message text. |
231 | | `sfSymbolName`     | String?                     | nil                          | Optional name of an SF Symbol. |
232 | | `sfSymbolSize`     | CGFloat?                    | 24                          | Size of the SF Symbol icon. |
233 | | `sfSymbolColor`    | Color?                      | .white                       | Color of the SF Symbol icon. |
234 | | `transitionType`   | ToastTransitionType         | .move(edge: .top)            | The transition animation for how the toast appears/disappears. |
235 | | `animation`        | Animation                   | .snappy                      | Animation used to present and dismiss the toast. |
236 | | `vDirection`       | VerticalDirection           | .top                         | Vertical position of the toast (`.top` or `.bottom`). |
237 | | `maxWidth`         | Bool                        | false                       | If `true`, toast takes maximum available width. |
238 | | `layoutDirection`  | LayoutDirection             | .leftToRight                 | Layout direction of content (`.leftToRight` or `.rightToLeft`). |
239 | -----------
240 | 
241 | ### Toast with custom icon or image
242 | 
243 | 
244 | ##### simple toast
245 | ```swift
246 |  VStack {
247 |       
248 |     }
249 |     .toastWithIcon(
250 |       isVisible: $isVisivble,
251 |       title: "with custom icon",
252 |       iconName: "swift"
253 |     )
254 | ```
255 | 
256 | ##### with full parameters
257 | ```swift
258 |  VStack {
259 |       
260 |     }
261 |     .toastWithIcon(
262 |       isVisible: $showWithCustomIconToast,
263 |       title: "with custom icon",
264 |       toastColor: .custom(.orange),
265 |       iconName: "swift",
266 |       iconSize: 18,
267 |       iconColor: nil,
268 |       transitionType: .move(edge: .top),
269 |       animation: .bouncy,
270 |       vDirection: .top,
271 |       titleFontColor: .white,
272 |       maxWidth: false,
273 |       layoutDirection: .leftToRight
274 |     )
275 | ```
276 | ##### Configuration ⚙️
277 | 
278 | | Parameter          | Type                         | Default Value                  | Description |
279 | |-------------------|------------------------------|--------------------------------|-------------|
280 | | `isVisible`        | Binding             | —                              | Binding to control visibility. |
281 | | `title`            | String                     | —                              | The main message displayed in the toast. |
282 | | `toastColor`       | ToastColorTypes             | .success                   | The color type or style of the toast (`.success`, `.error`, `.info`, `.warning`, `.custom(Color)`). |
283 | | `iconName`         | String?                    | nil                       | Optional name of a custom icon (from asset). |
284 | | `iconSize`         | CGFloat?                   | 24                          | Size of the custom icon. |
285 | | `iconColor`        | Color?                    | .white                     | Color of the custom icon. |
286 | | `transitionType`   | ToastTransitionType         | .move(edge: .top)            | The transition animation for how the toast appears/disappears. |
287 | | `animation`        | Animation                | .snappy                     | Animation used to present and dismiss the toast. |
288 | | `vDirection`       | VerticalDirection           | .top                        | Vertical position of the toast (`.top` or `.bottom`). |
289 | | `titleFontColor`   | Color                       | .white                       | The color of the toast message text. |
290 | | `maxWidth`         | Bool                       | false                      | If `true`, toast takes maximum available width. |
291 | | `layoutDirection`  | LayoutDirection             | .leftToRight                 | Layout direction of content (`.leftToRight` or `.rightToLeft`). |
292 | 
293 | ------------
294 | 
295 | ## 🍞 Toast Stack
296 | 
297 | 
298 | 
299 | ### With ToastStackManager, you can show toasts at the same time! 
300 | 
301 | #### Certainly, you can utilize toast stacks from your view model. Here’s an example: 
302 | ```swift
303 | // in your ViewModel
304 | 
305 | import ToastKit
306 | import Combine
307 | 
308 | final class ViewModel: ObservableObject {
309 |   let toastManager: ToastStackManager
310 |   
311 |   init(toastManager: ToastStackManager = ToastStackManager()) {
312 |     self.toastManager = toastManager
313 |   }
314 |   
315 |   @MainActor func foo() {
316 |     // Your logic
317 |     toastManager.show(title: "foo success toast", toastColor: .success, autoDisappearDuration: 3.0)
318 |     
319 |     // Your logic
320 |     toastManager.show(title: "foo info toast", toastColor: .info)
321 |   }
322 | }
323 | ```
324 | 
325 | #### in your view
326 | 
327 | ```swift
328 | import ToastKit
329 | import SwiftUI
330 | 
331 | struct ProfileView: View {
332 |   @StateObject private var vm: ViewModel
333 |   
334 |   init(vm: ViewModel = ViewModel()) {
335 |     _vm = StateObject(wrappedValue: vm)
336 |   }
337 |   
338 |   var body: some View {
339 |     ZStack {
340 |       // Your view
341 |       
342 |       ToastStackView(vm: vm.toastManager)
343 |       // Alternatively, you can utilize it with a custom transition.
344 |       ToastStackView(vm: vm.toastManager, transitionType: .move(edge: .trailing).combined(with: .opacity))
345 |     }
346 |   }
347 | }
348 | ```
349 | 
350 | ##### ToastStackView Configuration ⚙️
351 | | Parameter          | Type                         | Default Value                  | Description |
352 | |-------------------|------------------------------|--------------------------------|-------------|
353 | | `title`            | String                     | -                              | The main message displayed in the toast. |
354 | | `toastColor`           | ToastColorTypes        | -                              | The color type or style of the toast. |
355 | | `autoDisappearDuration`| TimeInterval           | 2.0                            | Duration before toast disappears. |
356 | 
357 | ##### ToastStackManager Configuration ⚙️
358 | | Parameter          | Type                         | Default Value                  | Description |
359 | |-------------------|------------------------------|--------------------------------|-------------|
360 | | `vm`            | ToastStackManager               | -                              | The view model that manages |
361 | | `transitionType`| AnyTransition                   | -                              | Transition animation for appearing/disappearing. |
362 | 
363 | -----------
364 | 
365 | 
366 | ## ⚠️ Alternatively, you can utilize the `.toast` method to construct a fully customizable toast by specifying the following parameters:
367 | 
368 | ##### Configuration ⚙️
369 | | Parameter          | Type                         | Default Value                  | Description |
370 | |-------------------|------------------------------|--------------------------------|-------------|
371 | | `isVisible`            | Binding               | —                                | Binding to control visibility. |
372 | | `title`                | String                      | —                                | The main toast message. |
373 | | `toastColor`           | ToastColorTypes             | `.success`                       | The color type or style of the toast. |
374 | | `transitionType`       | ToastTransitionType         | `.move(edge: .top)`              | Transition animation for appearing/disappearing. |
375 | | `animation`            | Animation                   | `.snappy`                        | Animation used to show/hide the toast. |
376 | | `autoDisappear`        | Bool                        | `true`                           | If `true`, toast disappears automatically. |
377 | | `autoDisappearDuration`| TimeInterval                | `2.0`                            | Duration before toast disappears. |
378 | | `maxWidth`             | Bool                        | `false`                          | If `true`, toast takes maximum width. |
379 | | `subtitle`             | String                      | `""`                             | Subtitle text for additional info. |
380 | | `font`                 | String                      | `"SFProDisplay"`                 | Name of the font used in text. |
381 | | `titleFontSize`        | CGFloat                     | `16`                             | Font size of the title. |
382 | | `titleFontWeight`      | Font.Weight                 | `.semibold`                      | Font weight of the title. |
383 | | `titleFontColor`       | Color                       | `.white`                         | Font color of the title. |
384 | | `subtitleFontSize`     | CGFloat                     | `14`                             | Font size of the subtitle. |
385 | | `subtitleFontWeight`   | Font.Weight                 | `.regular`                       | Font weight of the subtitle. |
386 | | `subtitleFontColor`    | Color                       | `.white`                         | Font color of the subtitle. |
387 | | `multilineTextAlignment`| TextAlignment             | `.center`                        | Alignment of multiline text. |
388 | | `innerHpadding`        | CGFloat                     | `20`                             | Inner horizontal padding. |
389 | | `innerVpadding`        | CGFloat                     | `10`                             | Inner vertical padding. |
390 | | `outterHpadding`       | CGFloat                     | `20`                             | Outer horizontal padding. |
391 | | `stackAligment`        | Alignment                   | `.top`                           | Stack alignment inside the toast. |
392 | | `isStackMaxHeight`     | Bool                        | `true`                           | occupies the maximum available height | 
393 | | `cornerRadius`         | CGFloat                     | `12`                             | Corner radius of the toast. |
394 | | `shadowColor`          | Color                       | `.black.opacity(0.2)`            | Shadow color. |
395 | | `shadowRadius`         | CGFloat                     | `10`                             | Radius of the toast's shadow. |
396 | | `shadowX`              | CGFloat                     | `0`                              | Horizontal offset of shadow. |
397 | | `shadowY`              | CGFloat                     | `4`                              | Vertical offset of shadow. |
398 | | `withIcon`             | Bool                        | `false`                          | Whether to show a custom icon. |
399 | | `iconName`             | String?                     | `nil`                            | Name of the custom icon. |
400 | | `iconSize`             | CGFloat?                    | `nil`                            | Size of the custom icon. |
401 | | `iconColor`            | Color?                      | `nil`                            | Color of the custom icon. |
402 | | `withSfsymbol`         | Bool                        | `false`                          | Whether to show an SF Symbol. |
403 | | `sfSymbolName`         | String?                     | `nil`                            | Name of the SF Symbol. |
404 | | `sfSymbolSize`         | CGFloat?                    | `nil`                            | Size of the SF Symbol. |
405 | | `sfSymbolColor`        | Color?                      | `nil`                            | Color of the SF Symbol. |
406 | | `layoutDirection`      | LayoutDirection             | `.leftToRight`                   | Layout direction of content. |
407 | | `closeSFicon`          | String                      | `"x.circle"`                     | SF Symbol used as close button. |
408 | | `closeSFiconSize`      | CGFloat                     | `18`                             | Size of the close icon. |
409 | | `closeSFiconColor`     | Color                       | `.white`                         | Color of the close icon. |
410 | ----
411 | ## Installation via Swift Package Manager 🖥️
412 | - Open your project.
413 | - Go to File → Add Package Dependencies.
414 | - Enter URL: https://github.com/Desp0o/ToastKit.git
415 | - Click Add Package.
416 | 
417 | ## Contact 📬
418 | 
419 | - Email: tornike.despotashvili@gmail.com
420 | - LinkedIn: https://www.linkedin.com/in/tornike-despotashvili-250150219/
421 | - github: https://github.com/Desp0o
422 | 
423 | 
424 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/Enums/ToastEnums.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  ToastTransitionType.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 15.04.25.
 6 | //
 7 | 
 8 | import SwiftUI
 9 | 
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public enum ToastTransitionType {
13 |   case fade
14 |   case scale
15 |   case slide
16 |   case move(edge: Edge)
17 |   case custom(AnyTransition)
18 | }
19 | 
20 | @available(macOS 14.0, *)
21 | @available(iOS 17, *)
22 | public enum ToastColorTypes {
23 |   case success
24 |   case warning
25 |   case error
26 |   case info
27 |   case custom(Color)
28 |   case glass
29 |   
30 |   var value: Color {
31 |     switch self {
32 |     case .success:
33 |       return .green
34 |     case .warning:
35 |       return .yellow
36 |     case .error:
37 |       return .red
38 |     case .info:
39 |       return .blue
40 |     case .glass:
41 |       return .clear
42 |     case .custom(let color):
43 |       return color
44 |     }
45 |   }
46 | }
47 | 
48 | @available(macOS 14.0, *)
49 | @available(iOS 17, *)
50 | public enum HorizontalDirection {
51 |   case leading
52 |   case trailing
53 |   
54 |   var value: Edge {
55 |     switch self {
56 |     case .leading:
57 |       return .leading
58 |     case .trailing:
59 |       return .trailing
60 |     }
61 |   }
62 | }
63 | 
64 | @available(macOS 14.0, *)
65 | @available(iOS 17, *)
66 | public enum VerticalDirection {
67 |   case top
68 |   case bottom
69 |   
70 |   var value: Alignment {
71 |     switch self {
72 |     case .top:
73 |       return .top
74 |     case .bottom:
75 |       return .bottom
76 |     }
77 |   }
78 | }
79 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/Extensions/Extension+View.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  File.swift
  3 | //  ToastKit
  4 | //
  5 | //  Created by Despo on 15.04.25.
  6 | //
  7 | 
  8 | import SwiftUI
  9 | 
 10 | @available(macOS 14.0, *)
 11 | @available(iOS 17.0, *)
 12 | 
 13 | public extension View {
 14 |   @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View {
 15 |     if condition {
 16 |       transform(self)
 17 |     } else {
 18 |       self
 19 |     }
 20 |   }
 21 | }
 22 | 
 23 | @available(macOS 14.0, *)
 24 | @available(iOS 17, *)
 25 | public extension View {
 26 |   func toast(
 27 |     isVisible: Binding,
 28 |     title: String,
 29 |     toastColor: ToastColorTypes = .success,
 30 |     transitionType: ToastTransitionType = .move(edge: .top),
 31 |     animation: Animation = .snappy,
 32 |     autoDisappear: Bool = true,
 33 |     autoDisappearDuration: TimeInterval = 2.0,
 34 |     maxWidth: Bool = false,
 35 |     subtitle: String = "",
 36 |     font: String = "SFProDisplay",
 37 |     titleFontSize: CGFloat = 16,
 38 |     titleFontWeight: Font.Weight = .semibold,
 39 |     titleFontColor: Color = .white,
 40 |     subtitleFontSize: CGFloat = 14,
 41 |     subtitleFontWeight: Font.Weight = .regular,
 42 |     subtitleFontColor: Color = .white,
 43 |     multilineTextAlignment: TextAlignment = .center,
 44 |     innerHpadding: CGFloat = 30,
 45 |     innerVpadding: CGFloat = 10,
 46 |     outterHpadding: CGFloat = 20,
 47 |     stackAligment: Alignment = .top,
 48 |     cornerRadius: CGFloat = 12,
 49 |     shadowColor: Color = .black.opacity(0.2),
 50 |     shadowRadius: CGFloat = 10,
 51 |     shadowX: CGFloat = 0,
 52 |     shadowY: CGFloat = 4,
 53 |     withIcon: Bool = false,
 54 |     iconName: String? = nil,
 55 |     iconSize: CGFloat? = nil,
 56 |     iconColor: Color? = nil,
 57 |     withSfsymbol: Bool = false,
 58 |     sfSymbolName: String? = "x.circle",
 59 |     sfSymbolSize: CGFloat? = 18,
 60 |     sfSymbolColor: Color? = .white,
 61 |     layoutDirection: LayoutDirection = .leftToRight,
 62 |     closeSFicon: String = "x.circle",
 63 |     closeSFiconSize: CGFloat = 18,
 64 |     closeSFiconColor: Color = .white,
 65 |     isGlass: Bool = false,
 66 |     glassColor: Color = .clear
 67 |   ) -> some View {
 68 |     modifier(
 69 |       ToastModifier(
 70 |         isVisible: isVisible,
 71 |         toast: CustomToast(
 72 |           isVisible: isVisible,
 73 |           title: title,
 74 |           toastColor: toastColor,
 75 |           transitionType: transitionType,
 76 |           animation: animation,
 77 |           autoDisappear: autoDisappear,
 78 |           autoDisappearDuration: autoDisappearDuration,
 79 |           maxWidth: maxWidth,
 80 |           subtitle: subtitle,
 81 |           font: font,
 82 |           titleFontSize: titleFontSize,
 83 |           titleFontWeight: titleFontWeight,
 84 |           titleFontColor: titleFontColor,
 85 |           subtitleFontSize: subtitleFontSize,
 86 |           subtitleFontWeight: subtitleFontWeight,
 87 |           subtitleFontColor: subtitleFontColor,
 88 |           multilineTextAlignment: multilineTextAlignment,
 89 |           innerHpadding: innerHpadding,
 90 |           innerVpadding: innerVpadding,
 91 |           outterHpadding: outterHpadding,
 92 |           stackAligment: stackAligment,
 93 |           cornerRadius: cornerRadius,
 94 |           shadowColor: shadowColor,
 95 |           shadowRadius: shadowRadius,
 96 |           shadowX: shadowX,
 97 |           shadowY: shadowY,
 98 |           withIcon: withIcon,
 99 |           iconName: iconName,
100 |           iconSize: iconSize,
101 |           iconColor: iconColor,
102 |           withSfsymbol: withSfsymbol,
103 |           sfSymbolName: sfSymbolName,
104 |           sfSymbolSize: sfSymbolSize,
105 |           sfSymbolColor: sfSymbolColor,
106 |           layoutDirection: layoutDirection,
107 |           closeSFicon: closeSFicon,
108 |           closeSFiconSize: closeSFiconSize,
109 |           closeSFiconColor: closeSFiconColor,
110 |           isGlass: isGlass,
111 |           glassColor: glassColor
112 |         )
113 |       )
114 |     )
115 |   }
116 | }
117 | 
118 | 
119 | @available(macOS 14.0, *)
120 | @available(iOS 17, *)
121 | public extension View {
122 |   func successToast(
123 |     isVisible: Binding,
124 |     title: String,
125 |     toastColor: ToastColorTypes = .success,
126 |     animation: Animation = .snappy,
127 |     titleFontColor: Color = .white,
128 |     maxWidth: Bool = false
129 |   ) -> some View {
130 |     toast(
131 |       isVisible: isVisible,
132 |       title: title,
133 |       toastColor: toastColor,
134 |       animation: animation,
135 |       maxWidth: maxWidth,
136 |       titleFontColor: titleFontColor
137 |     )
138 |   }
139 | }
140 | 
141 | 
142 | @available(macOS 14.0, *)
143 | @available(iOS 17, *)
144 | public extension View {
145 |   func warningToast(
146 |     isVisible: Binding,
147 |     title: String,
148 |     toastColor: ToastColorTypes = .warning,
149 |     animation: Animation = .snappy,
150 |     titleFontColor: Color = .white,
151 |     maxWidth: Bool = false
152 |   ) -> some View {
153 |     toast(
154 |       isVisible: isVisible,
155 |       title: title,
156 |       toastColor: toastColor,
157 |       animation: animation,
158 |       maxWidth: maxWidth,
159 |       titleFontColor: titleFontColor
160 |     )
161 |   }
162 | }
163 | 
164 | 
165 | @available(macOS 14.0, *)
166 | @available(iOS 17, *)
167 | public extension View {
168 |   func errorToast(
169 |     isVisible: Binding,
170 |     title: String,
171 |     toastColor: ToastColorTypes = .error,
172 |     animation: Animation = .snappy,
173 |     titleFontColor: Color = .white,
174 |     maxWidth: Bool = false
175 |   ) -> some View {
176 |     toast(
177 |       isVisible: isVisible,
178 |       title: title,
179 |       toastColor: toastColor,
180 |       animation: animation,
181 |       maxWidth: maxWidth,
182 |       titleFontColor: titleFontColor
183 |     )
184 |   }
185 | }
186 | 
187 | 
188 | @available(macOS 14.0, *)
189 | @available(iOS 17, *)
190 | public extension View {
191 |   func bottomToast(
192 |     isVisible: Binding,
193 |     title: String,
194 |     toastColor: ToastColorTypes = .success,
195 |     animation: Animation = .snappy,
196 |     titleFontColor: Color = .white,
197 |     maxWidth: Bool = false
198 |   ) -> some View {
199 |     toast(
200 |       isVisible: isVisible,
201 |       title: title,
202 |       toastColor: toastColor,
203 |       transitionType: .move(edge: .bottom),
204 |       animation: animation,
205 |       maxWidth: maxWidth,
206 |       titleFontColor: titleFontColor,
207 |       stackAligment: .bottom
208 |     )
209 |   }
210 | }
211 | 
212 | 
213 | @available(macOS 14.0, *)
214 | @available(iOS 17, *)
215 | public extension View {
216 |   func edgeSlideToast(
217 |     isVisible: Binding,
218 |     title: String,
219 |     toastColor: ToastColorTypes = .success,
220 |     animation: Animation = .snappy,
221 |     hDirection: HorizontalDirection = .trailing,
222 |     vDirection: VerticalDirection = .top,
223 |     titleFontColor: Color = .white,
224 |     maxWidth: Bool = false
225 |   ) -> some View {
226 |     toast(
227 |       isVisible: isVisible,
228 |       title: title,
229 |       toastColor: toastColor,
230 |       transitionType: .move(edge: hDirection.value),
231 |       animation: animation,
232 |       maxWidth: maxWidth,
233 |       titleFontColor: titleFontColor,
234 |       stackAligment: vDirection.value
235 |     )
236 |   }
237 | }
238 | 
239 | 
240 | @available(macOS 14.0, *)
241 | @available(iOS 17, *)
242 | public extension View {
243 |   func infoToast(
244 |     isVisible: Binding,
245 |     title: String,
246 |     toastColor: ToastColorTypes = .info,
247 |     animation: Animation = .snappy,
248 |     titleFontColor: Color = .white,
249 |     maxWidth: Bool = false
250 |   ) -> some View {
251 |     toast(
252 |       isVisible: isVisible,
253 |       title: title,
254 |       toastColor: .info,
255 |       animation: animation,
256 |       maxWidth: maxWidth,
257 |       titleFontColor: titleFontColor
258 |     )
259 |   }
260 | }
261 | 
262 | 
263 | @available(macOS 14.0, *)
264 | @available(iOS 17, *)
265 | public extension View {
266 |   func toastWithIcon(
267 |     isVisible: Binding,
268 |     title: String,
269 |     toastColor: ToastColorTypes = .success,
270 |     iconName: String?,
271 |     iconSize: CGFloat? = 24,
272 |     iconColor: Color? = nil,
273 |     transitionType: ToastTransitionType = .move(edge: .top),
274 |     animation: Animation = .snappy,
275 |     vDirection: VerticalDirection = .top,
276 |     titleFontColor: Color = .white,
277 |     maxWidth: Bool = false
278 |   ) -> some View {
279 |     toast(
280 |       isVisible: isVisible,
281 |       title: title,
282 |       toastColor: toastColor,
283 |       transitionType: transitionType,
284 |       animation: animation,
285 |       maxWidth: maxWidth,
286 |       titleFontColor: titleFontColor,
287 |       stackAligment: vDirection.value,
288 |       withIcon: true,
289 |       iconName: iconName,
290 |       iconSize: iconSize,
291 |       iconColor: iconColor,
292 |     )
293 |   }
294 | }
295 | 
296 | 
297 | @available(macOS 14.0, *)
298 | @available(iOS 17, *)
299 | public extension View {
300 |   func toastWithSFSymbol(
301 |     isVisible: Binding,
302 |     title: String,
303 |     toastColor: ToastColorTypes = .success,
304 |     titleFontColor: Color = .white,
305 |     sfSymbolName: String?,
306 |     sfSymbolSize: CGFloat? = 24,
307 |     sfSymbolColor: Color? = .white,
308 |     transitionType: ToastTransitionType = .move(edge: .top),
309 |     animation: Animation = .snappy,
310 |     vDirection: VerticalDirection = .top,
311 |     maxWidth: Bool = false,
312 |     layoutDirection: LayoutDirection = .leftToRight
313 |   ) -> some View {
314 |     toast(
315 |       isVisible: isVisible,
316 |       title: title,
317 |       toastColor: toastColor,
318 |       transitionType: transitionType,
319 |       animation: animation,
320 |       maxWidth: maxWidth,
321 |       titleFontColor: titleFontColor,
322 |       stackAligment: vDirection.value,
323 |       withSfsymbol: true,
324 |       sfSymbolName: sfSymbolName,
325 |       sfSymbolSize: sfSymbolSize,
326 |       sfSymbolColor: sfSymbolColor,
327 |       layoutDirection: layoutDirection
328 |     )
329 |   }
330 | }
331 | 
332 | 
333 | @available(macOS 26.0, *)
334 | @available(iOS 26, *)
335 | public extension View {
336 |   func glassToast(
337 |     isVisible: Binding,
338 |     title: String,
339 |     subtitle: String = "",
340 |     glassColor: Color = .clear,
341 |     titleFontColor: Color = .white,
342 |     subtitleFontColor: Color = .white,
343 |     maxWidth: Bool = false,
344 |     transitionType: ToastTransitionType = .move(edge: .top),
345 |     animation: Animation = .snappy,
346 |     vDirection: VerticalDirection = .top,
347 |     layoutDirection: LayoutDirection = .leftToRight
348 |   ) -> some View {
349 |     modifier(
350 |       ToastModifier(
351 |         isVisible: isVisible,
352 |         toast: CustomToast(
353 |           isVisible: isVisible,
354 |           title: title,
355 |           toastColor: .glass,
356 |           transitionType: transitionType,
357 |           animation: animation,
358 |           maxWidth: maxWidth,
359 |           subtitle: subtitle,
360 |           titleFontColor: titleFontColor,
361 |           subtitleFontColor: subtitleFontColor,
362 |           stackAligment: vDirection.value,
363 |           layoutDirection: layoutDirection, isGlass: true,
364 |           glassColor: glassColor
365 |         )
366 |       )
367 |     )
368 |   }
369 | }
370 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastKit.swift:
--------------------------------------------------------------------------------
  1 | // The Swift Programming Language
  2 | // https://docs.swift.org/swift-book
  3 | 
  4 | import SwiftUI
  5 | 
  6 | @available(macOS 14.0, *)
  7 | @available(iOS 17, *)
  8 | public struct CustomToast: View {
  9 |   @State private var disappearTask: Task<(), Never>?
 10 |   @Binding var isVisible: Bool
 11 |   let title: String
 12 |   let toastColor: ToastColorTypes
 13 |   let transitionType: ToastTransitionType
 14 |   let animation: Animation
 15 |   let autoDisappear: Bool
 16 |   let autoDisappearDuration: TimeInterval
 17 |   let maxWidth: Bool
 18 |   
 19 |   let subtitle: String
 20 |   
 21 |   let font: String
 22 |   let titleFontSize: CGFloat
 23 |   let titleFontWeight: Font.Weight
 24 |   let titleFontColor: Color
 25 |   
 26 |   let subtitleFontSize: CGFloat
 27 |   let subtitleFontWeight: Font.Weight
 28 |   let subtitleFontColor: Color
 29 |   
 30 |   let multilineTextAlignment: TextAlignment
 31 |   
 32 |   let innerHpadding: CGFloat
 33 |   let innerVpadding: CGFloat
 34 |   let outterHpadding: CGFloat
 35 |   let stackAligment: Alignment
 36 |   let isStackMaxHeight: Bool
 37 |   
 38 |   let cornerRadius: CGFloat
 39 |   
 40 |   let shadowColor: Color
 41 |   let shadowRadius: CGFloat
 42 |   let shadowX: CGFloat
 43 |   let shadowY: CGFloat
 44 |   
 45 |   let withIcon: Bool
 46 |   let iconName: String?
 47 |   let iconSize: CGFloat?
 48 |   let iconColor: Color?
 49 |   
 50 |   let withSfsymbol: Bool
 51 |   let sfSymbolName: String?
 52 |   let sfSymbolSize: CGFloat?
 53 |   let sfSymbolColor: Color?
 54 |   
 55 |   let layoutDirection: LayoutDirection
 56 |   
 57 |   let closeSFicon: String
 58 |   let closeSFiconSize: CGFloat
 59 |   let closeSFiconColor: Color
 60 |   
 61 |   let isGlass: Bool
 62 |   let glassColor: Color
 63 |   
 64 |   init(
 65 |     isVisible: Binding,
 66 |     title: String,
 67 |     toastColor: ToastColorTypes = .success,
 68 |     transitionType: ToastTransitionType = .move(edge: .top),
 69 |     animation: Animation = .snappy,
 70 |     autoDisappear: Bool = true,
 71 |     autoDisappearDuration: TimeInterval = 2.0,
 72 |     maxWidth: Bool = false,
 73 |     
 74 |     subtitle: String = "",
 75 |     
 76 |     font: String = "SFProDisplay",
 77 |     titleFontSize: CGFloat = 16,
 78 |     titleFontWeight: Font.Weight = .regular,
 79 |     titleFontColor: Color = .white,
 80 |     
 81 |     subtitleFontSize: CGFloat = 14,
 82 |     subtitleFontWeight: Font.Weight = .regular,
 83 |     subtitleFontColor: Color = .white,
 84 |     
 85 |     multilineTextAlignment: TextAlignment = .center,
 86 |     
 87 |     innerHpadding: CGFloat = 20,
 88 |     innerVpadding: CGFloat = 10,
 89 |     outterHpadding: CGFloat = 20,
 90 |     stackAligment: Alignment = .top,
 91 |     isStackMaxHeight: Bool = true,
 92 |     
 93 |     cornerRadius: CGFloat = 12,
 94 |     
 95 |     shadowColor: Color = .black.opacity(0.2),
 96 |     shadowRadius: CGFloat = 10,
 97 |     shadowX: CGFloat = 0,
 98 |     shadowY: CGFloat = 4,
 99 |     
100 |     withIcon: Bool = false,
101 |     iconName: String? = nil,
102 |     iconSize: CGFloat? = nil,
103 |     iconColor: Color? = nil,
104 |     
105 |     withSfsymbol: Bool = false,
106 |     sfSymbolName: String? = nil,
107 |     sfSymbolSize: CGFloat? = nil,
108 |     sfSymbolColor: Color? = nil,
109 |     
110 |     layoutDirection: LayoutDirection = .leftToRight,
111 |     
112 |     closeSFicon: String = "x.circle",
113 |     closeSFiconSize: CGFloat = 18,
114 |     closeSFiconColor: Color = .white,
115 |     
116 |     isGlass: Bool = false,
117 |     glassColor: Color = .clear
118 |   ) {
119 |     _isVisible = isVisible
120 |     self.title = title
121 |     self.toastColor = toastColor
122 |     self.transitionType = transitionType
123 |     self.subtitle = subtitle
124 |     self.autoDisappear = autoDisappear
125 |     self.autoDisappearDuration = autoDisappearDuration
126 |     self.animation = animation
127 |     self.maxWidth = maxWidth
128 |     
129 |     self.font = font
130 |     self.titleFontSize = titleFontSize
131 |     self.titleFontWeight = titleFontWeight
132 |     self.titleFontColor = titleFontColor
133 |     
134 |     self.subtitleFontSize = subtitleFontSize
135 |     self.subtitleFontWeight = subtitleFontWeight
136 |     self.subtitleFontColor = subtitleFontColor
137 |     
138 |     self.multilineTextAlignment = multilineTextAlignment
139 |     
140 |     self.innerHpadding = innerHpadding
141 |     self.innerVpadding = innerVpadding
142 |     self.outterHpadding = outterHpadding
143 |     self.stackAligment = stackAligment
144 |     self.isStackMaxHeight = isStackMaxHeight
145 |     
146 |     self.cornerRadius = cornerRadius
147 |     
148 |     self.shadowColor = shadowColor
149 |     self.shadowRadius = shadowRadius
150 |     self.shadowX = shadowX
151 |     self.shadowY = shadowY
152 |     
153 |     self.withIcon = withIcon
154 |     self.iconName = iconName
155 |     self.iconSize = iconSize
156 |     self.iconColor = iconColor
157 |     
158 |     self.withSfsymbol = withSfsymbol
159 |     self.sfSymbolName = sfSymbolName
160 |     self.sfSymbolSize = sfSymbolSize
161 |     self.sfSymbolColor = sfSymbolColor
162 |     
163 |     self.layoutDirection = layoutDirection
164 |     
165 |     self.closeSFicon = closeSFicon
166 |     self.closeSFiconSize = closeSFiconSize
167 |     self.closeSFiconColor = closeSFiconColor
168 |     
169 |     self.isGlass = isGlass
170 |     self.glassColor = glassColor
171 |   }
172 |   
173 |   public var body: some View {
174 |     ZStack(alignment: stackAligment) {
175 |       if isVisible {
176 |         HStack {
177 |           if !withIcon && !withSfsymbol {
178 |             VStack {
179 |               Text(title)
180 |                 .font(.custom(font, size: titleFontSize))
181 |                 .font(.system(size: titleFontSize))
182 |                 .fontWeight(titleFontWeight)
183 |                 .foregroundStyle(titleFontColor)
184 |                 .multilineTextAlignment(multilineTextAlignment)
185 |               
186 |               if !subtitle.isEmpty {
187 |                 Text(subtitle)
188 |                   .font(.custom(font, size: subtitleFontSize))
189 |                   .fontWeight(subtitleFontWeight)
190 |                   .foregroundStyle(subtitleFontColor)
191 |                   .multilineTextAlignment(multilineTextAlignment)
192 |               }
193 |             }
194 |           } else {
195 |             HStack(spacing: 20) {
196 |               if withSfsymbol {
197 |                 Image(systemName: sfSymbolName ?? "")
198 |                   .renderingMode(.template)
199 |                   .resizable()
200 |                   .scaledToFit()
201 |                   .frame(width: sfSymbolSize, height: sfSymbolSize)
202 |                   .foregroundStyle(sfSymbolColor ?? .clear)
203 |               } else {
204 |                 Image(iconName ?? "")
205 |                   .resizable()
206 |                   .renderingMode(iconColor != nil ? .template : .original)
207 |                   .scaledToFit()
208 |                   .foregroundStyle(iconColor ?? .clear)
209 |                   .frame(width: iconSize, height: iconSize)
210 |               }
211 |               
212 |               Text(title)
213 |                 .font(.custom(font, size: titleFontSize))
214 |                 .fontWeight(titleFontWeight)
215 |                 .foregroundStyle(titleFontColor)
216 |                 .multilineTextAlignment(multilineTextAlignment)
217 |             }
218 |             .environment(\.layoutDirection, layoutDirection)
219 |           }
220 |         }
221 |         .padding(.horizontal, innerHpadding)
222 |         .padding(.vertical, innerVpadding)
223 |         .if(maxWidth) { $0.frame(maxWidth: .infinity)}
224 |         .background(toastColor.value)
225 |         .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
226 |         .transition(transition(for: transitionType))
227 |         .shadow(color: shadowColor, radius: shadowRadius, x: shadowX, y: shadowY)
228 |         .overlay {
229 |           if !autoDisappear {
230 |             ZStack {
231 |               Button {
232 |                 isVisible = false
233 |               } label: {
234 |                 Image(systemName: closeSFicon)
235 |                   .renderingMode(.template)
236 |                   .resizable()
237 |                   .scaledToFit()
238 |                   .frame(width: closeSFiconSize, height: closeSFiconSize)
239 |                   .foregroundStyle(closeSFiconColor)
240 |               }
241 |               .offset(x: -7, y: 10)
242 |             }
243 |             .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
244 |           }
245 |         }
246 |         .padding(.horizontal, outterHpadding)
247 |         .background {
248 |           if #available(iOS 26.0, *), #available(macOS 26.0, *), isGlass {
249 |             Color.clear.glassEffect(.regular.tint(glassColor))
250 |           }
251 |         }
252 |       }
253 |     }
254 |     .frame(maxWidth: .infinity, alignment: stackAligment)
255 |     .if(isStackMaxHeight) { $0.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: stackAligment)}
256 |     .onChange(of: isVisible) { _, newValue in
257 |       if newValue {
258 |         disappearTask?.cancel()
259 |         if autoDisappear {
260 |           disappearTask = Task {
261 |             try? await Task.sleep(nanoseconds: UInt64(autoDisappearDuration * 1_000_000_000))
262 |             if !Task.isCancelled {
263 |               isVisible = false
264 |             }
265 |           }
266 |         }
267 |       } else {
268 |         disappearTask?.cancel()
269 |         disappearTask = nil
270 |       }
271 |     }
272 |     .animation(animation, value: isVisible)
273 |   }
274 |   
275 |   func transition(for type: ToastTransitionType) -> AnyTransition {
276 |     switch type {
277 |     case .fade:
278 |       return .opacity
279 |     case .scale:
280 |       return .scale
281 |     case .slide:
282 |       return .slide
283 |     case .move(let edge):
284 |       return .move(edge: edge).combined(with: .opacity)
285 |     case .custom(let transition):
286 |       return transition
287 |     }
288 |   }
289 | }
290 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastModifier.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  ToastModifier.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 16.04.25.
 6 | //
 7 | 
 8 | import SwiftUI
 9 | 
10 | @available(macOS 14.0, *)
11 | @available(iOS 17.0, *)
12 | public struct ToastModifier: ViewModifier {
13 |   @Binding var isVisible: Bool
14 |   let toast: CustomToast
15 |   
16 |   public func body(content: Content) -> some View {
17 |     content
18 |       .overlay {
19 |         toast
20 |       }
21 |   }
22 | }
23 | 
24 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastItemModel.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  ToastItemModel.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 18.04.25.
 6 | //
 7 | 
 8 | import SwiftUI
 9 | 
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public struct ToastItemModel: Identifiable, Equatable {
13 |   public let id = UUID()
14 |   let title: String
15 |   let toastColor: ToastColorTypes
16 |   let autoDisappearDuration: TimeInterval
17 |   let isStackMaxHeight: Bool = false
18 |   
19 |   public static func == (lhs: ToastItemModel, rhs: ToastItemModel) -> Bool {
20 |     return lhs.id == rhs.id
21 |   }
22 | }
23 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastStackManager.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  ToastStackManager.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 18.04.25.
 6 | //
 7 | import SwiftUI
 8 | 
 9 | @available(macOS 14.0, *)
10 | @available(iOS 17, *)
11 | 
12 | public class ToastStackManager: ObservableObject {
13 |   @Published var toasts: [ToastItemModel] = []
14 |   
15 |   public init() { }
16 |   
17 |   @MainActor public func show(title: String, toastColor: ToastColorTypes, autoDisappearDuration: TimeInterval = 2.0) {
18 |     let toast = ToastItemModel(
19 |       title: title,
20 |       toastColor: toastColor,
21 |       autoDisappearDuration: autoDisappearDuration
22 |     )
23 |     
24 |     toasts.insert(toast, at: 0)
25 |     
26 |     Task {
27 |       try await Task.sleep(nanoseconds: UInt64(autoDisappearDuration * 1_000_000_000))
28 |       self.removeToast(toast)
29 |     }
30 |   }
31 |   
32 |   func removeToast(_ toast: ToastItemModel) {
33 |     toasts.removeAll { $0.id == toast.id }
34 |   }
35 | }
36 | 
--------------------------------------------------------------------------------
/Sources/ToastKit/ToastStack/ToastStackView.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  ToastStackView.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 18.04.25.
 6 | //
 7 | 
 8 | import SwiftUI
 9 | 
10 | @available(macOS 14.0, *)
11 | @available(iOS 17, *)
12 | public struct ToastStackView: View {
13 |   @StateObject var vm: ToastStackManager
14 |   let transitionType: AnyTransition
15 |   let isGlass: Bool
16 |   let glassColor: Color
17 |   
18 |   public init(
19 |     vm: ToastStackManager,
20 |     transitionType: AnyTransition = .move(edge: .top).combined(with: .opacity),
21 |     isGlass: Bool = false,
22 |     glassColor: Color = .clear
23 |   ) {
24 |     _vm = StateObject(wrappedValue: vm)
25 |     self.transitionType = transitionType
26 |     self.isGlass = isGlass
27 |     self.glassColor = glassColor
28 |   }
29 |   
30 |   public var body: some View {
31 |     VStack {
32 |       ForEach(vm.toasts, id: \.id) { toast in
33 |         ZStack {
34 |           if #available(iOS 26.0, *), isGlass {
35 |             CustomToast(
36 |               isVisible: .constant(true),
37 |               title: toast.title,
38 |               toastColor: toast.toastColor,
39 |               isStackMaxHeight: toast.isStackMaxHeight,
40 |               isGlass: isGlass,
41 |               glassColor: glassColor
42 |             )
43 |           } else {
44 |             CustomToast(
45 |               isVisible: .constant(true),
46 |               title: toast.title,
47 |               toastColor: toast.toastColor,
48 |               isStackMaxHeight: toast.isStackMaxHeight
49 |             )
50 |           }
51 |         }
52 |         .transition(transitionType)
53 |       }
54 |     }
55 |     .frame(maxWidth: .infinity, maxHeight: .infinity,alignment: .top)
56 |     .animation(.bouncy, value: vm.toasts)
57 |   }
58 | }
59 | 
--------------------------------------------------------------------------------
/Tests/ToastKitTests/EnumsTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  EnumsTests.swift
 3 | //  ToastKit
 4 | //
 5 | //  Created by Despo on 18.04.25.
 6 | //
 7 | 
 8 | import XCTest
 9 | @testable import ToastKit
10 | 
11 | final class ToastColorTypesTests: XCTestCase {
12 |   func testSuccessType() {
13 |     XCTAssertEqual(ToastColorTypes.success.value, .green)
14 |   }
15 |   
16 |   func testWarninType() {
17 |     XCTAssertEqual(ToastColorTypes.warning.value, .yellow)
18 |   }
19 |   
20 |   func testErrorType() {
21 |     XCTAssertEqual(ToastColorTypes.error.value, .red)
22 |   }
23 |   
24 |   func testInfoType() {
25 |     XCTAssertEqual(ToastColorTypes.info.value, .blue)
26 |   }
27 |   
28 |   func testCustomType() {
29 |     XCTAssertEqual(ToastColorTypes.custom(.teal).value, .teal)
30 |   }
31 | }
32 | 
33 | final class ToastDirectionsTests: XCTestCase {
34 |   func testLeadingDirection() {
35 |     XCTAssertEqual(HorizontalDirection.leading.value, .leading)
36 |   }
37 |   
38 |   func testTrailinDirection() {
39 |     XCTAssertEqual(HorizontalDirection.trailing.value, .trailing)
40 |   }
41 | }
42 | 
43 | final class VerticalDirectionTests: XCTestCase {
44 |   func testTopDirection() {
45 |     XCTAssertEqual(VerticalDirection.top.value, .top)
46 |   }
47 |   
48 |   func testBottomDirection() {
49 |     XCTAssertEqual(VerticalDirection.bottom.value, .bottom)
50 |   }
51 | }
52 | 
--------------------------------------------------------------------------------
/Tests/ToastKitTests/ToastStackTests.swift:
--------------------------------------------------------------------------------
 1 | import XCTest
 2 | @testable import ToastKit
 3 | 
 4 | final class ToastStackTests: XCTestCase {
 5 |   private var sut: ToastStackManager!
 6 |   
 7 |   override func setUpWithError() throws {
 8 |     sut = ToastStackManager()
 9 |   }
10 |   
11 |   override func tearDownWithError() throws {
12 |     sut = nil
13 |   }
14 |   
15 |   @MainActor func testAddToastToStack() throws {
16 |     //Given
17 |     XCTAssertTrue(sut.toasts.isEmpty)
18 |     
19 |     //When
20 |     sut.show(title: "test", toastColor: .success, autoDisappearDuration: 2)
21 |     
22 |     //Then
23 |     XCTAssertEqual(sut.toasts.count, 1)
24 |   }
25 |   
26 |   @MainActor func testToastDissapearWithDuration() async throws {
27 |     // Given
28 |     sut.show(title: "Auto Disappear", toastColor: .info, autoDisappearDuration: 2.0)
29 |     XCTAssertEqual(sut.toasts.count, 1)
30 |     
31 |     Task {
32 |       // When
33 |       try await Task.sleep(nanoseconds: 2_000_000_000)
34 |       
35 |       // Then
36 |       XCTAssertEqual(sut.toasts.count, 0)
37 |     }
38 |   }
39 |   
40 |   func testRemoveToastFromToasts() throws {
41 |     //Given
42 |     let toast1 = ToastItemModel(title: "One", toastColor: .info, autoDisappearDuration: 2.0)
43 |     sut.toasts = [toast1]
44 |     
45 |     XCTAssertEqual(sut.toasts.count, 1)
46 | 
47 |     //When
48 |     sut.removeToast(toast1)
49 |     
50 |     //Then
51 |     XCTAssertTrue(sut.toasts.isEmpty)
52 |   }
53 | }
54 | 
--------------------------------------------------------------------------------