├── .eslintrc.js
├── .php-cs-fixer.dist.php
├── .stylelintrc
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README-v1.md
├── README.md
├── babel.config.js
├── bootstrap
└── app.php
├── builds
└── cdn.js
├── composer.json
├── config
└── tall-toasts.php
├── dist
└── js
│ ├── manifest.json
│ ├── tall-toasts.js
│ └── tall-toasts.js.map
├── package.json
├── phpcs.xml.dist
├── phpmd-ruleset.xml.dist
├── phpstan.neon.dist
├── resources
├── js
│ └── tall-toasts.js
└── views
│ ├── includes
│ ├── content.blade.php
│ └── icon.blade.php
│ └── livewire
│ └── toasts.blade.php
├── rollup.config.js
└── src
├── Concerns
└── WireToast.php
├── Controllers
├── CanPretendToBeAFile.php
└── JavaScriptAssets.php
├── Livewire
└── ToastComponent.php
├── Notification.php
├── NotificationType.php
├── Toast.php
├── ToastBladeDirectives.php
├── ToastManager.php
├── ToastServiceProvider.php
└── helpers.php
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es2021: true
6 | },
7 | extends: [
8 | 'standard'
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | parserOptions: {
12 | ecmaVersion: 12
13 | },
14 | plugins: [
15 | '@typescript-eslint'
16 | ],
17 | rules: {
18 | semi: ['error', 'always'],
19 | quotes: [
20 | 'error',
21 | 'single',
22 | {
23 | avoidEscape: true,
24 | allowTemplateLiterals: true
25 | }
26 | ]
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in([
5 | __DIR__ . '/src',
6 | __DIR__ . '/tests',
7 | ])
8 | ->name('*.php')
9 | ->notName('*.blade.php')
10 | ->ignoreDotFiles(true)
11 | ->ignoreVCS(true);
12 |
13 | return (new PhpCsFixer\Config())->setRules([
14 | '@PSR12' => true,
15 | 'array_syntax' => ['syntax' => 'short'],
16 | 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']],
17 | 'no_unused_imports' => true,
18 | 'not_operator_with_successor_space' => true,
19 | 'trailing_comma_in_multiline' => true,
20 | 'phpdoc_scalar' => true,
21 | 'unary_operator_spaces' => true,
22 | 'binary_operator_spaces' => true,
23 | 'blank_line_before_statement' => [
24 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
25 | ],
26 | 'phpdoc_single_line_var_spacing' => true,
27 | 'phpdoc_var_without_name' => true,
28 | 'class_attributes_separation' => [
29 | 'elements' => [
30 | 'method' => 'one',
31 | ],
32 | ],
33 | 'method_argument_space' => [
34 | 'on_multiline' => 'ensure_fully_multiline',
35 | 'keep_multiple_spaces_after_comma' => true,
36 | ],
37 | 'single_trait_insert_per_statement' => true,
38 | ])
39 | ->setFinder($finder);
40 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "at-rule-empty-line-before": [
4 | "always",
5 | {
6 | "except": [
7 | "blockless-after-same-name-blockless",
8 | "first-nested"
9 | ]
10 | }
11 | ],
12 | "at-rule-name-case": "lower",
13 | "at-rule-name-newline-after": "always-multi-line",
14 | "at-rule-name-space-after": "always-single-line",
15 | "at-rule-semicolon-space-before": "never",
16 | "block-no-empty": true,
17 | "color-no-invalid-hex": true,
18 | "comment-empty-line-before": [
19 | "always",
20 | {
21 | "ignore": [
22 | "after-comment"
23 | ]
24 | }
25 | ],
26 | "comment-no-empty": true,
27 | "comment-whitespace-inside": "always",
28 | "declaration-block-no-duplicate-properties": true,
29 | "declaration-block-no-redundant-longhand-properties": true,
30 | "declaration-colon-space-after": "always",
31 | "font-family-no-duplicate-names": true,
32 | "font-weight-notation": "numeric",
33 | "indentation": [
34 | 2,
35 | {
36 | "ignore": [
37 | "value"
38 | ]
39 | }
40 | ],
41 | "length-zero-no-unit": true,
42 | "max-empty-lines": 2,
43 | "max-line-length": 100,
44 | "max-nesting-depth": 3,
45 | "no-duplicate-selectors": true,
46 | "no-eol-whitespace": true,
47 | "no-extra-semicolons": true,
48 | "no-invalid-double-slash-comments": true,
49 | "no-missing-end-of-source-newline": true,
50 | "property-case": "lower",
51 | "property-no-unknown": true,
52 | "rule-empty-line-before": [
53 | "always",
54 | {
55 | "except": [
56 | "first-nested"
57 | ],
58 | "ignore": [
59 | "after-comment"
60 | ]
61 | }
62 | ],
63 | "selector-list-comma-newline-after": "always",
64 | "selector-pseudo-class-no-unknown": true,
65 | "shorthand-property-no-redundant-values": true,
66 | "string-no-newline": true,
67 | "string-quotes": "double",
68 | "unit-case": "lower",
69 | "unit-no-unknown": true,
70 | "unit-whitelist": [
71 | "deg",
72 | "em",
73 | "rem",
74 | "%",
75 | "ms",
76 | "s",
77 | "px",
78 | "vh",
79 | "vw"
80 | ],
81 | "value-keyword-case": "lower"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for
6 | everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity
7 | and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion,
8 | or sexual identity and orientation.
9 |
10 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to a positive environment for our community include:
15 |
16 | - Demonstrating empathy and kindness toward other people
17 | - Being respectful of differing opinions, viewpoints, and experiences
18 | - Giving and gracefully accepting constructive feedback
19 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
20 | - Focusing on what is best not just for us as individuals, but for the overall community
21 |
22 | Examples of unacceptable behavior include:
23 |
24 | - The use of sexualized language or imagery, and sexual attention or advances of any kind
25 | - Trolling, insulting or derogatory comments, and personal or political attacks
26 | - Public or private harassment
27 | - Publishing others' private information, such as a physical or email address, without their explicit permission
28 | - Other conduct which could reasonably be considered inappropriate in a professional setting
29 |
30 | ## Enforcement Responsibilities
31 |
32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take
33 | appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive,
34 | or harmful.
35 |
36 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
37 | issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for
38 | moderation decisions when appropriate.
39 |
40 | ## Scope
41 |
42 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing
43 | the community in public spaces. Examples of representing our community include using an official e-mail address, posting
44 | via an official social media account, or acting as an appointed representative at an online or offline event.
45 |
46 | ## Enforcement
47 |
48 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible
49 | for enforcement at . All complaints will be reviewed and investigated promptly and fairly.
50 |
51 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
52 |
53 | ## Enforcement Guidelines
54 |
55 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem
56 | in violation of this Code of Conduct:
57 |
58 | ### 1. Correction
59 |
60 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the
61 | community.
62 |
63 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation
64 | and an explanation of why the behavior was inappropriate. A public apology may be requested.
65 |
66 | ### 2. Warning
67 |
68 | **Community Impact**: A violation through a single incident or series of actions.
69 |
70 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including
71 | unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding
72 | interactions in community spaces as well as external channels like social media. Violating these terms may lead to a
73 | temporary or permanent ban.
74 |
75 | ### 3. Temporary Ban
76 |
77 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
78 |
79 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified
80 | period of time. No public or private interaction with the people involved, including unsolicited interaction with those
81 | enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
82 |
83 | ### 4. Permanent Ban
84 |
85 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate
86 | behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
87 |
88 | **Consequence**: A permanent ban from any sort of public interaction within the community.
89 |
90 | ## Attribution
91 |
92 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at
93 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
94 |
95 | Community Impact Guidelines were inspired
96 | by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
97 |
98 | For answers to common questions about this code of conduct, see the FAQ at
99 | https://www.contributor-covenant.org/faq. Translations are available at
100 | https://www.contributor-covenant.org/translations.
101 |
102 | [homepage]: https://www.contributor-covenant.org
103 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) @usernotnull
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-v1.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Beautiful Notification Toasts For Laravel
4 |
5 | A Toast global that can be called from the backend (via Controllers, Blade Views, Components) or frontend (JS, Alpine
6 | Components) to render customizable toasts.
7 |
8 | Runs with the TALL stack: [Laravel](https://laravel.com/docs/10.x/installation),
9 | [TailwindCSS](https://tailwindcss.com/docs/guides/laravel),
10 | [Livewire](https://laravel-livewire.com/docs/2.x/installation),
11 | [AlpineJS](https://alpinejs.dev/essentials/installation).
12 |
13 | [](https://github.com/usernotnull/tall-toasts/releases)
14 | [](https://github.com/usernotnull/tall-toasts/blob/main/dist/js/tall-toasts.js)
15 | [](https://scrutinizer-ci.com/g/usernotnull/tall-toasts)
16 | [](https://www.codacy.com/gh/usernotnull/tall-toasts/dashboard?utm_source=github.com&utm_medium=referral&utm_content=usernotnull/tall-toasts&utm_campaign=Badge_Grade)
17 | [](https://app.codecov.io/gh/usernotnull/tall-toasts)
18 |
19 | Light | Dark
20 | ------------ | -------------
21 |  | 
22 |
23 |
24 |
25 | ## Featured On
26 |
27 | [](https://laravel-news.com/toast-notifications-for-the-tall-stack) [](https://madewithlaravel.com/tall-toasts) [](https://podcasts.apple.com/us/podcast/laravel-news-podcast/id1051289963?mt=2)
28 |
29 | ## Why
30 |
31 | If you are building a web app with the TALL stack, you must choose this library over the other outdated libraries
32 | available:
33 |
34 | ### Size does matter
35 |
36 | Since the frontend is a pure AlpineJS component with no reliance on external JS libs, and since the backend handles most
37 | of the logic, the javascript footprint is
38 | tiny [(less than ONE kilobyte!)](https://img.badgesize.io/usernotnull/tall-toasts/main/dist/js/tall-toasts.js.svg?compression=brotli&style=plastic&color=green&label=JS%20size)
39 | .
40 |
41 | The CSS footprint is also negligible as it uses the default TailwindCSS classes. Even if you override the default views,
42 | you will rest assured that Tailwind's purging will only keep the styles/classes you have used.
43 |
44 | In plain English, it will not bloat your generated JS/CSS files nor add extra files to download as when using other JS
45 | libs!
46 |
47 | ### Takes advantage of all the niceties that come with TALL
48 |
49 | You can call it from anywhere! Memorize `Toast` for the frontend and `toast()` for the backend.
50 |
51 | See the [usage section](#usage) for examples.
52 |
53 | ### Customizable
54 |
55 | You have control over the view: As you are overriding the blade view, you'll be able to shape it as you like using
56 | TailwindCSS classes.
57 |
58 | No more messing with custom CSS overrides!
59 |
60 | ## Usage
61 |
62 | ### From The Frontend
63 |
64 | ```js
65 | Toast.info('Notification from the front-end...', 'The Title');
66 |
67 | Toast.success('A toast without a title also works');
68 |
69 | Toast.warning('Watch out!');
70 |
71 | Toast.danger('I warned you!', 'Yikes');
72 |
73 | Toast.debug('I will NOT show in production! Locally, I will also log in console...', 'A Debug Message');
74 |
75 | Toast.success('This toast will display only for 3 seconds', 'The Title', 3000);
76 |
77 | Toast.success('This toast will display until you remove it manually', 'The Title', 0);
78 | ```
79 |
80 | ### From The Backend
81 |
82 | ```php
83 | toast()
84 | ->info('I will appear only on the next page!')
85 | ->pushOnNextPage();
86 |
87 | toast()
88 | ->info('Notification from the backend...', 'The Title')
89 | ->push();
90 |
91 | toast()
92 | ->success('A toast without a title also works')
93 | ->push();
94 |
95 | toast()
96 | ->warning('Watch out!')
97 | ->push();
98 |
99 | toast()
100 | ->danger('I warned you!', 'Yikes')
101 | ->push();
102 |
103 | toast()
104 | ->danger('I will go… to the next line 💪', 'I am HOT')
105 | ->doNotSanitize()
106 | ->push();
107 |
108 | toast()
109 | ->debug('I will NOT show in production! Locally, I will also log in console...', 'A Debug Message')
110 | ->push();
111 |
112 | // debug also accepts objects as message
113 | toast()
114 | ->debug(User::factory()->createOne()->only(['name', 'email']), 'A User Dump')
115 | ->push();
116 |
117 | toast()
118 | ->success('This toast will display only for 3 seconds')
119 | ->duration(3000)
120 | ->push();
121 |
122 | toast()
123 | ->success('This toast will display until you remove it manually')
124 | ->sticky()
125 | ->push();
126 | ```
127 |
128 | You can call the above toast helper from controllers, blade views, and components.
129 |
130 | **To properly call it from inside livewire components, add the trait:**
131 |
132 | ```php
133 | use Livewire\Component;
134 | use Usernotnull\Toast\Concerns\WireToast;
135 |
136 | class DemoComponent extends Component
137 | {
138 | use WireToast; // <-- add this
139 |
140 | public function sendCookie(): void
141 | {
142 | toast()
143 | ->success('You earned a cookie! 🍪')
144 | ->pushOnNextPage();
145 |
146 | redirect()->route('dashboard');
147 | }
148 | ```
149 |
150 | ## Support Me
151 |
152 | I plan on developing many open-source packages using the TALL stack.
153 | Consider supporting my work by tweeting about this library or by contributing to this package.
154 |
155 | Check out the list of other packages I built for the TALL stack [Other Packages](#other-packages).
156 | To stay updated, [follow me on Twitter](https://twitter.com/usernotnull).
157 |
158 | ## Requirements
159 |
160 | Dependency | Version
161 | ----|----
162 | PHP | ^8.0
163 | Laravel | ^8.0 \| ^9.0 \| ^10.0
164 | TailwindCSS | ^2.0 \| ^3.0
165 | Livewire | ^2.0
166 | AlpineJS | ^3.0
167 |
168 | ## Installation
169 |
170 | You can install the package via [Composer](https://getcomposer.org/):
171 |
172 | ```bash
173 | composer require usernotnull/tall-toasts
174 | ```
175 |
176 | ## Setup
177 |
178 | ### TailwindCSS
179 |
180 | Build your CSS as you usually do, ie
181 |
182 | ```bash
183 | npm run dev
184 | ```
185 |
186 | #### Usage With Tailwind JIT
187 |
188 | If you are using [Just-in-Time Mode](https://tailwindcss.com/docs/just-in-time-mode), add these additional lines into
189 | your `tailwind.config.js` file:
190 |
191 | ```js
192 | // use `purge` instead of `content` if using TailwindCSS v2.x
193 | content: [
194 | './vendor/usernotnull/tall-toasts/config/**/*.php',
195 | './vendor/usernotnull/tall-toasts/resources/views/**/*.blade.php',
196 | // etc...
197 | ]
198 | ```
199 |
200 | This way, Tailwind JIT will include the classes used in this library in your CSS.
201 |
202 | *As usual, if the content of `tailwind.config.js` changes, you should re-run the npm command.*
203 |
204 | ### Registering Toast with AlpineJS
205 |
206 | Next, you need to register `Toast` with AlpineJS. How this is done depends on which method you used to add Alpine to your project:
207 |
208 | #### AlpineJS installed as an NPM Module
209 |
210 | If you have installed AlpineJS through NPM, you can add the Toast component by changing your `app.js` file to match:
211 |
212 | ```js
213 | import Alpine from "alpinejs"
214 | import ToastComponent from '../../vendor/usernotnull/tall-toasts/resources/js/tall-toasts'
215 |
216 | Alpine.data('ToastComponent', ToastComponent)
217 |
218 | window.Alpine = Alpine
219 | Alpine.start()
220 | ```
221 |
222 | *If you have a custom directory structure, you may have to adjust the above import path until it correctly points
223 | to `tall-toasts.js` inside this vendor file.*
224 |
225 | Include the `@toastScripts` blade directive *BEFORE* the `mix()` helper if using Laravel Mix, if using Vite, include it before the `@vite` blade directive.
226 |
227 | ```html
228 | @toastScripts
229 |
230 | <--- Vite --->
231 | @vite(['resources/css/app.css', 'resources/js/app.js'])
232 |
233 | <--- Mix --->
234 |
235 | ```
236 |
237 | #### AlpineJS added via script tag
238 |
239 | If you imported AlpineJS via a script tag simply add the `@toastScripts` blade directive *BEFORE* importing AlpineJS:
240 |
241 | ```html
242 | @toastScripts
243 |
244 | ```
245 |
246 | ### The View
247 |
248 | Add `` **as high as possible** in the body tag, ie:
249 |
250 | ```html
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | ```
266 |
267 | That's it! 🎉
268 |
269 | ***
270 |
271 | ## RTL Support
272 |
273 | The default layout now supports RTL.
274 |
275 | As per TailwindCSS docs on [RTL support](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support):
276 | `Always set the direction, even if left-to-right is your default`.
277 |
278 | ```html
279 |
280 |
281 |
282 | ```
283 |
284 | ## Customization
285 |
286 | The toasts should look pretty good out of the box. However, we've documented a couple of ways to customize the views and
287 | functionality.
288 |
289 | ### Configuration
290 |
291 | You can publish the config file with:
292 |
293 | ```bash
294 | php artisan vendor:publish --provider="Usernotnull\Toast\ToastServiceProvider" --tag="tall-toasts-config"
295 | ```
296 |
297 | These are the default contents of the published config file:
298 |
299 | ```php
300 | 5000,
307 |
308 | /*
309 | * How long to wait before displaying the toasts after page loads, in ms
310 | */
311 | 'load_delay' => 400,
312 | ];
313 |
314 | ```
315 |
316 | ### Customizing views
317 |
318 | You can publish and change all views in this package:
319 |
320 | ```bash
321 | php artisan vendor:publish --provider="Usernotnull\Toast\ToastServiceProvider" --tag="tall-toasts-views"
322 | ```
323 |
324 | The published views can be found and changed in `resources/views/vendor/tall-toast/`.
325 |
326 | The published files are:
327 |
328 | - `includes/content.blade.php` - *the content view of each popup notification, fully configurable*
329 | - `includes/icon.blade.php` - *the icons of each notification type*
330 | - `livewire/toasts.blade.php` - *the parent of all toasts*
331 |
332 | #### Text Sanitization
333 |
334 | The content view displays the title and message with x-html. This is fine since the backend sanitizes the title and
335 | message by default.
336 |
337 | ⚠️ If you wish to skip sanitization in order to display HTML content, such as bolding the text or adding ` ` to go to
338 | the next line, you will call doNotSanitize() as seen in the [usage section](#usage). In such case, make sure no user
339 | input is provided!
340 |
341 | ## Troubleshooting
342 |
343 | Make sure you thoroughly go through this readme first!
344 |
345 | If the checklist below does not resolve your problem, feel free
346 | to [submit an issue](https://github.com/usernotnull/tall-toasts/issues/new/choose). Make sure to follow the bug report
347 | template. It helps us quickly reproduce the bug and resolve it.
348 |
349 | ### The toasts show multiple times only after refresh
350 |
351 | - If you are calling toasts from a livewire component,
352 | did you add the trait WireToast to the component? [(see)](#from-the-backend)
353 |
354 | - Did you swap push() and pushOnNextPage()?
355 |
356 | ### The toasts won't show
357 |
358 | - Is the located in a page that has both the livewire and alpine/app.js script tags
359 | inserted? [(see)](#the-view)
360 |
361 | - Did you skip adding the ToastComponent as an alpine data component? [(see)](#alpinejs)
362 |
363 | - Did you forget calling push() at the end of the chained method? [(see)](#usage)
364 |
365 | - Have you tried calling the toast() helper function from another part of the application and check if it worked (it
366 | will help us scope the problem)? [(see)](#usage)
367 |
368 | - Did you try calling `php artisan view:clear`?
369 |
370 | - Are you getting any console errors?
371 |
372 | ### The toasts show but look weird
373 |
374 | - Are you using TailwindCSS JIT? Don't forget to update your purge list! [(see)](#usage-with-tailwind-jit)
375 | - You may need to rebuild your CSS by running: `npm run dev` or re-running `npm run watch` [(see)](#tailwindcss)
376 |
377 | ## Other Packages
378 |
379 | To stay updated, [follow me on Twitter](https://twitter.com/usernotnull).
380 |
381 | ## Testing
382 |
383 | This package uses [PestPHP](https://pestphp.com/) to run its tests.
384 |
385 | - To run tests without coverage, run:
386 |
387 | ```bash
388 | composer test
389 | ```
390 |
391 | - To run tests with coverage, run:
392 |
393 | ```bash
394 | composer test-coverage
395 | ```
396 |
397 | ## Contributing
398 |
399 | This package has 3 GitHub Workflows which run sequentially when pushing PRs:
400 |
401 | - First, it checks for styling issues and automatically fixes them using:
402 | 1. [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer)
403 | 2. [PHP Code Beautifier](https://github.com/squizlabs/PHP_CodeSniffer)
404 |
405 | - Then, it uses static analysis followed by standard unit tests using:
406 | 1. [Psalm](https://psalm.dev/)
407 | 2. [PHP Stan](https://github.com/phpstan/phpstan)
408 | 3. [PHP MessDetector](https://phpmd.org/)
409 | 4. [PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer)
410 | 5. [PestPHP](https://pestphp.com/)
411 |
412 | - Finally, it generates the minified JS dist which is injected by @toastScripts
413 |
414 | When pushing PRs, it's a good idea to do a quick run and make sure the workflow checks out, which saves time during code
415 | review before merging.
416 |
417 | To facilitate the job, you can run the below command before pushing the PR:
418 |
419 | ```bash
420 | composer workflow
421 | ```
422 |
423 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
424 |
425 | ## Changelog
426 |
427 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
428 |
429 | ## Versioning
430 |
431 | This project follows the [Semantic Versioning](https://semver.org/) guidelines.
432 |
433 | ## Security Vulnerabilities
434 |
435 | As per security best practices, do not call `doNotSanitize()` on a toast that has user input in its message or title!
436 |
437 | Please review [the security policy](https://github.com/usernotnull/tall-toasts/security/policy) on how to report
438 | security vulnerabilities.
439 |
440 | ## Credits
441 |
442 | - [John F](https://github.com/usernotnull) ( [@usernotnull](https://twitter.com/usernotnull) )
443 | - [All Contributors](../../contributors)
444 |
445 | ## License
446 |
447 | The MIT License (MIT). Please see [the license file](LICENSE.md) for more information.
448 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Beautiful Notification Toasts For Laravel & Livewire
4 |
5 | A Toast global that can be called from the backend (via Controllers, Blade Views, Components) or frontend (JS, Alpine
6 | Components) to render customizable toasts.
7 |
8 | Runs with the TALL stack: [Laravel](https://laravel.com/docs/10.x/installation),
9 | [TailwindCSS](https://tailwindcss.com/docs/guides/laravel),
10 | [Livewire](https://laravel-livewire.com/docs/2.x/installation),
11 | [AlpineJS](https://alpinejs.dev/essentials/installation).
12 |
13 | [](https://github.com/usernotnull/tall-toasts/releases)
14 | [](https://github.com/usernotnull/tall-toasts/blob/main/dist/js/tall-toasts.js)
15 | [](https://scrutinizer-ci.com/g/usernotnull/tall-toasts)
16 | [](https://www.codacy.com/gh/usernotnull/tall-toasts/dashboard?utm_source=github.com&utm_medium=referral&utm_content=usernotnull/tall-toasts&utm_campaign=Badge_Grade)
17 | [](https://app.codecov.io/gh/usernotnull/tall-toasts)
18 |
19 | Light | Dark
20 | ------------ | -------------
21 |  | 
22 |
23 |
24 |
25 | ## Featured On
26 |
27 | [](https://laravel-news.com/toast-notifications-for-the-tall-stack) [](https://madewithlaravel.com/tall-toasts) [](https://podcasts.apple.com/us/podcast/laravel-news-podcast/id1051289963?mt=2)
28 |
29 | ## Why
30 |
31 | If you are building a web app with the TALL stack, you must choose this library over the other outdated libraries
32 | available:
33 |
34 | ### Size does matter
35 |
36 | Since the frontend is a pure AlpineJS component with no reliance on external JS libs, and since the backend handles most
37 | of the logic, the javascript footprint is
38 | tiny [(less than ONE kilobyte!)](https://img.badgesize.io/usernotnull/tall-toasts/main/dist/js/tall-toasts.js.svg?compression=brotli&style=plastic&color=green&label=JS%20size)
39 | .
40 |
41 | The CSS footprint is also negligible as it uses the default TailwindCSS classes. Even if you override the default views,
42 | you will rest assured that Tailwind's purging will only keep the styles/classes you have used.
43 |
44 | In plain English, it will not bloat your generated JS/CSS files nor add extra files to download as when using other JS
45 | libs!
46 |
47 | ### Takes advantage of all the niceties that come with TALL
48 |
49 | You can call it from anywhere! Memorize `Toast` for the frontend and `toast()` for the backend.
50 |
51 | See the [usage section](#usage) for examples.
52 |
53 | ### Customizable
54 |
55 | You have control over the view: As you are overriding the blade view, you'll be able to shape it as you like using
56 | TailwindCSS classes.
57 |
58 | No more messing with custom CSS overrides!
59 |
60 | ## Usage
61 |
62 | ### From The Frontend
63 |
64 | ```js
65 | Toast.info('Notification from the front-end...', 'The Title');
66 |
67 | Toast.success('A toast without a title also works');
68 |
69 | Toast.warning('Watch out!');
70 |
71 | Toast.danger('I warned you!', 'Yikes');
72 |
73 | Toast.debug('I will NOT show in production! Locally, I will also log in console...', 'A Debug Message');
74 |
75 | Toast.success('This toast will display only for 3 seconds', 'The Title', 3000);
76 |
77 | Toast.success('This toast will display until you remove it manually', 'The Title', 0);
78 | ```
79 |
80 | ### From The Backend
81 |
82 | ```php
83 | toast()
84 | ->info('I will appear only on the next page!')
85 | ->pushOnNextPage();
86 |
87 | toast()
88 | ->info('Notification from the backend...', 'The Title')
89 | ->push();
90 |
91 | toast()
92 | ->success('A toast without a title also works')
93 | ->push();
94 |
95 | toast()
96 | ->warning('Watch out!')
97 | ->push();
98 |
99 | toast()
100 | ->danger('I warned you!', 'Yikes')
101 | ->push();
102 |
103 | toast()
104 | ->danger('I will go… to the next line 💪', 'I am HOT')
105 | ->doNotSanitize()
106 | ->push();
107 |
108 | toast()
109 | ->debug('I will NOT show in production! Locally, I will also log in console...', 'A Debug Message')
110 | ->push();
111 |
112 | // debug also accepts objects as message
113 | toast()
114 | ->debug(User::factory()->createOne()->only(['name', 'email']), 'A User Dump')
115 | ->push();
116 |
117 | toast()
118 | ->success('This toast will display only for 3 seconds')
119 | ->duration(3000)
120 | ->push();
121 |
122 | toast()
123 | ->success('This toast will display until you remove it manually')
124 | ->sticky()
125 | ->push();
126 | ```
127 |
128 | You can call the above toast helper from controllers, blade views, and components.
129 |
130 | ## Support Me
131 |
132 | I plan on developing many open-source packages using the TALL stack.
133 | Consider supporting my work by tweeting about this library or by contributing to this package.
134 |
135 | Check out the list of other packages I built for the TALL stack [Other Packages](#other-packages).
136 | To stay updated, [follow me on Twitter](https://twitter.com/usernotnull).
137 |
138 | ## Requirements
139 |
140 | Dependency | Version
141 | ----|----
142 | PHP | ^8.0
143 | Laravel | ^8.0 \| ^9.0 \| ^10.0 \| ^11.0 \| ^12.0
144 | TailwindCSS | ^2.0 \| ^3.0
145 | Livewire | ^2.0 \| ^3.0 (as of tall-toasts v2)
146 | AlpineJS | ^3.0
147 |
148 | You can find the older [v1 documentation here](README-v1.md)
149 |
150 | ## Installation
151 |
152 | You can install the package via [Composer](https://getcomposer.org/):
153 |
154 | ```bash
155 | composer require usernotnull/tall-toasts
156 | ```
157 |
158 | ## Setup
159 |
160 | ### TailwindCSS
161 |
162 | Build your CSS as you usually do, ie
163 |
164 | ```bash
165 | npm run dev
166 | ```
167 |
168 | #### Usage With Tailwind JIT
169 |
170 | If you are using [Just-in-Time Mode](https://tailwindcss.com/docs/just-in-time-mode), add these additional lines into
171 | your `tailwind.config.js` file:
172 |
173 | ```js
174 | // use `purge` instead of `content` if using TailwindCSS v2.x
175 | content: [
176 | './vendor/usernotnull/tall-toasts/config/**/*.php',
177 | './vendor/usernotnull/tall-toasts/resources/views/**/*.blade.php',
178 | // etc...
179 | ]
180 | ```
181 |
182 | This way, Tailwind JIT will include the classes used in this library in your CSS.
183 |
184 | *As usual, if the content of `tailwind.config.js` changes, you should re-run the npm command.*
185 |
186 | ### Registering Toast with AlpineJS
187 |
188 | Add the Toast component in your `app.js`:
189 |
190 | ```js
191 | import {Alpine, Livewire} from '../../vendor/livewire/livewire/dist/livewire.esm';
192 | import ToastComponent from '../../vendor/usernotnull/tall-toasts/resources/js/tall-toasts'
193 |
194 | Alpine.plugin(ToastComponent)
195 |
196 | Livewire.start()
197 | ```
198 |
199 | Add `` **as high as possible** in the body tag, ie:
200 |
201 | ```html
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | @livewireScriptConfig
215 |
216 |
217 |
218 | ```
219 |
220 | To properly dispatch toasts from inside your livewire components, **add the trait**:
221 |
222 | ```php
223 | use Livewire\Component;
224 | use Usernotnull\Toast\Concerns\WireToast;
225 |
226 | class DemoComponent extends Component
227 | {
228 | use WireToast; // <-- add this
229 |
230 | public function sendCookie(): void
231 | {
232 | toast()
233 | ->success('You earned a cookie! 🍪')
234 | ->pushOnNextPage();
235 |
236 | redirect()->route('dashboard');
237 | }
238 | ```
239 |
240 | That's it! 🎉
241 |
242 | ## RTL Support
243 |
244 | The default layout now supports RTL.
245 |
246 | As per TailwindCSS docs on [RTL support](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support):
247 | `Always set the direction, even if left-to-right is your default`.
248 |
249 | ```html
250 |
251 |
252 |
253 | ```
254 |
255 | ## Customization
256 |
257 | The toasts should look pretty good out of the box. However, we've documented a couple of ways to customize the views and
258 | functionality.
259 |
260 | ### Configuration
261 |
262 | You can publish the config file with:
263 |
264 | ```bash
265 | php artisan vendor:publish --provider="Usernotnull\Toast\ToastServiceProvider" --tag="tall-toasts-config"
266 | ```
267 |
268 | These are the default contents of the published config file:
269 |
270 | ```php
271 | 5000,
278 |
279 | /*
280 | * How long to wait before displaying the toasts after page loads, in ms
281 | */
282 | 'load_delay' => 400,
283 | ];
284 |
285 | ```
286 |
287 | ### Customizing views
288 |
289 | You can publish and change all views in this package:
290 |
291 | ```bash
292 | php artisan vendor:publish --provider="Usernotnull\Toast\ToastServiceProvider" --tag="tall-toasts-views"
293 | ```
294 |
295 | The published views can be found and changed in `resources/views/vendor/tall-toast/`.
296 |
297 | The published files are:
298 |
299 | - `includes/content.blade.php` - *the content view of each popup notification, fully configurable*
300 | - `includes/icon.blade.php` - *the icons of each notification type*
301 | - `livewire/toasts.blade.php` - *the parent of all toasts*
302 |
303 | #### Text Sanitization
304 |
305 | The content view displays the title and message with x-html. This is fine since the backend sanitizes the title and
306 | message by default.
307 |
308 | ⚠️ If you wish to skip sanitization in order to display HTML content, such as bolding the text or adding ` ` to go to
309 | the next line, you will call doNotSanitize() as seen in the [usage section](#usage). In such case, make sure no user
310 | input is provided!
311 |
312 | ## Troubleshooting
313 |
314 | Make sure you thoroughly go through this readme first!
315 |
316 | If the checklist below does not resolve your problem, feel free
317 | to [submit an issue](https://github.com/usernotnull/tall-toasts/issues/new/choose). Make sure to follow the bug report
318 | template. It helps us quickly reproduce the bug and resolve it.
319 |
320 | ### The toasts show multiple times only after refresh
321 |
322 | - If you are calling toasts from a livewire component,
323 | did you add the trait WireToast to the component? [(see)](#from-the-backend)
324 |
325 | - Did you swap push() and pushOnNextPage()?
326 |
327 | ### The toasts won't show
328 |
329 | - Is the located in a page that has both the livewire and alpine/app.js script tags
330 | inserted?
331 |
332 | - Did you skip adding the ToastComponent as an alpine data component?
333 |
334 | - Did you forget calling push() at the end of the chained method? [(see)](#usage)
335 |
336 | - Have you tried calling the toast() helper function from another part of the application and check if it worked (it
337 | will help us scope the problem)? [(see)](#usage)
338 |
339 | - Did you try calling `php artisan view:clear`?
340 |
341 | - Are you getting any console errors?
342 |
343 | ### The toasts show but look weird
344 |
345 | - Are you using TailwindCSS JIT? Don't forget to update your purge list! [(see)](#usage-with-tailwind-jit)
346 | - You may need to rebuild your CSS by running: `npm run dev` or re-running `npm run watch` [(see)](#tailwindcss)
347 |
348 | ## Other Packages
349 |
350 | To stay updated, [follow me on Twitter](https://twitter.com/usernotnull).
351 |
352 | ## Testing
353 |
354 | This package uses [PestPHP](https://pestphp.com/) to run its tests.
355 |
356 | - To run tests without coverage, run:
357 |
358 | ```bash
359 | composer test
360 | ```
361 |
362 | - To run tests with coverage, run:
363 |
364 | ```bash
365 | composer test-coverage
366 | ```
367 |
368 | ## Contributing
369 |
370 | This package has 3 GitHub Workflows which run sequentially when pushing PRs:
371 |
372 | - First, it checks for styling issues and automatically fixes them using:
373 | 1. [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer)
374 | 2. [PHP Code Beautifier](https://github.com/squizlabs/PHP_CodeSniffer)
375 |
376 | - Then, it uses static analysis followed by standard unit tests using:
377 | 1. [Psalm](https://psalm.dev/)
378 | 2. [PHP Stan](https://github.com/phpstan/phpstan)
379 | 3. [PHP MessDetector](https://phpmd.org/)
380 | 4. [PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer)
381 | 5. [PestPHP](https://pestphp.com/)
382 |
383 | - Finally, it generates the minified JS dist which is injected by @toastScripts
384 |
385 | When pushing PRs, it's a good idea to do a quick run and make sure the workflow checks out, which saves time during code
386 | review before merging.
387 |
388 | To facilitate the job, you can run the below command before pushing the PR:
389 |
390 | ```bash
391 | composer workflow
392 | ```
393 |
394 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
395 |
396 | ## Changelog
397 |
398 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
399 |
400 | ## Versioning
401 |
402 | This project follows the [Semantic Versioning](https://semver.org/) guidelines.
403 |
404 | ## Security Vulnerabilities
405 |
406 | As per security best practices, do not call `doNotSanitize()` on a toast that has user input in its message or title!
407 |
408 | Please review [the security policy](https://github.com/usernotnull/tall-toasts/security/policy) on how to report
409 | security vulnerabilities.
410 |
411 | ## Credits
412 |
413 | - [John F](https://github.com/usernotnull) ( [@usernotnull](https://twitter.com/usernotnull) )
414 | - [All Contributors](../../contributors)
415 |
416 | ## License
417 |
418 | The MIT License (MIT). Please see [the license file](LICENSE.md) for more information.
419 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current',
8 | edge: '18',
9 | ie: '11'
10 | }
11 | }
12 | ]
13 | ],
14 | plugins: [
15 | '@babel/plugin-proposal-object-rest-spread'
16 | ],
17 | env: {
18 | test: {
19 | presets: [
20 | [
21 | '@babel/preset-env',
22 | {
23 | targets: {
24 | node: 'current'
25 | }
26 | }
27 | ]
28 | ]
29 | }
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | {
4 | toast(window.Alpine);
5 | });
6 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "usernotnull/tall-toasts",
3 | "type": "library",
4 | "description": "A Toast notification library for the Laravel TALL stack. You can push notifications from the backend or frontend to render customizable toasts with almost zero footprint on the published CSS/JS!",
5 | "keywords": [
6 | "usernotnull",
7 | "tall-toasts",
8 | "toast-notifications",
9 | "ToastManager",
10 | "ui-components",
11 | "laravel-package",
12 | "laravel",
13 | "livewire",
14 | "tailwindcss",
15 | "alpinejs",
16 | "tall-stack"
17 | ],
18 | "homepage": "https://github.com/usernotnull/tall-toasts",
19 | "license": "MIT",
20 | "authors": [
21 | {
22 | "name": "John F (@usernotnull)",
23 | "email": "15612814+usernotnull@users.noreply.github.com",
24 | "role": "Developer"
25 | }
26 | ],
27 | "require": {
28 | "php": "^8.0|^8.1",
29 | "illuminate/contracts": "^8.15 || 9.0 - 9.34 || ^9.36 || ^10.0|^11.0|^12.0",
30 | "spatie/laravel-package-tools": "^1.19",
31 | "livewire/livewire": "^v3"
32 | },
33 | "require-dev": {
34 | "pestphp/pest": "^1.23.1|^3.7",
35 | "nunomaduro/larastan": "^2.9",
36 | "laravel/pint": "^1.21",
37 | "nunomaduro/collision": "^6.4.0",
38 | "orchestra/testbench": "^8.23.2|^10.0",
39 | "pestphp/pest-plugin-laravel": "^1.4.0|^3.1",
40 | "friendsofphp/php-cs-fixer": "^v3.71",
41 | "pestphp/pest-plugin-parallel": "^1.2",
42 | "phpmd/phpmd": "^2.15",
43 | "squizlabs/php_codesniffer": "^3.11",
44 | "vimeo/psalm": "^5.26.1|^6.6"
45 | },
46 | "autoload": {
47 | "files": [
48 | "src/helpers.php"
49 | ],
50 | "psr-4": {
51 | "Usernotnull\\Toast\\": "src"
52 | }
53 | },
54 | "autoload-dev": {
55 | "psr-4": {
56 | "Usernotnull\\Toast\\Tests\\": "tests"
57 | }
58 | },
59 | "scripts": {
60 | "test": "./vendor/bin/pest --no-coverage",
61 | "workflow": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcs && ./vendor/bin/phpcbf && ./vendor/bin/psalm --output-format=github && ./vendor/bin/phpstan && ./vendor/bin/phpmd src github phpmd-ruleset.xml.dist && XDEBUG_MODE=coverage vendor/bin/pest --parallel --coverage --min=100",
62 | "test-coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --parallel --coverage --min=100"
63 | },
64 | "config": {
65 | "sort-packages": true,
66 | "allow-plugins": {
67 | "pestphp/pest-plugin": true,
68 | "composer/package-versions-deprecated": true
69 | }
70 | },
71 | "extra": {
72 | "laravel": {
73 | "providers": [
74 | "Usernotnull\\Toast\\ToastServiceProvider"
75 | ],
76 | "aliases": {
77 | "Toast": "ToastManager"
78 | }
79 | }
80 | },
81 | "minimum-stability": "dev",
82 | "prefer-stable": true
83 | }
84 |
--------------------------------------------------------------------------------
/config/tall-toasts.php:
--------------------------------------------------------------------------------
1 | 5000,
8 |
9 | /*
10 | * How long to wait before displaying the toasts after page loads, in ms
11 | */
12 | 'load_delay' => 400,
13 |
14 | /*
15 | * Session keys used.
16 | * No need to edit unless the keys are already being used and conflict.
17 | */
18 | 'session_keys' => [
19 | 'toasts' => 'toasts',
20 | 'toasts_next_page' => 'toasts-next',
21 | ],
22 | ];
23 |
--------------------------------------------------------------------------------
/dist/js/manifest.json:
--------------------------------------------------------------------------------
1 | {"/tall-toasts.js":"/tall-toasts.js?id=8cebbd1d7ec8bfb4c7d2"}
--------------------------------------------------------------------------------
/dist/js/tall-toasts.js:
--------------------------------------------------------------------------------
1 | !function(factory){"function"==typeof define&&define.amd?define(factory):factory()}((function(){"use strict";document.addEventListener("alpine:initializing",(function(){window.Alpine.data("ToastComponent",(function($wire){return{defaultDuration:$wire.defaultDuration,wireToasts:$wire.$entangle("toasts"),prod:$wire.$entangle("prod"),wireToastsIndex:0,toasts:[],pendingToasts:[],pendingRemovals:[],count:0,loaded:!1,init:function(){var _this=this;window.Toast={component:this,make:function(message,title,type,duration){return{title:title,message:message,type:type,duration:duration}},debug:function(message){var title=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",duration=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;this.component.add(this.make(message,title,"debug",null!=duration?duration:this.component.defaultDuration))},info:function(message){var title=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",duration=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;this.component.add(this.make(message,title,"info",null!=duration?duration:this.component.defaultDuration))},success:function(message){var title=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",duration=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;this.component.add(this.make(message,title,"success",null!=duration?duration:this.component.defaultDuration))},warning:function(message){var title=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",duration=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;this.component.add(this.make(message,title,"warning",null!=duration?duration:this.component.defaultDuration))},danger:function(message){var title=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",duration=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;this.component.add(this.make(message,title,"danger",null!=duration?duration:this.component.defaultDuration))}},addEventListener("toast",(function(event){_this.add(event.detail)})),this.fetchWireToasts(),this.$watch("wireToasts",(function(){_this.fetchWireToasts()})),setTimeout((function(){_this.loaded=!0,_this.pendingToasts.forEach((function(toast){_this.add(toast)})),_this.pendingToasts=null}),$wire.loadDelay)},fetchWireToasts:function(){var _this2=this;this.wireToasts.forEach((function(toast,i){i<_this2.wireToastsIndex||(_this2.add(window.Alpine.raw(toast)),_this2.wireToastsIndex++)}))},add:function(toast){var _toast$type;if(!0===this.loaded){if("debug"===toast.type){if(this.prod)return;console.log(toast.title,toast.message)}null!==(_toast$type=toast.type)&&void 0!==_toast$type||(toast.type="info"),toast.show=0,toast.index=this.count,this.toasts[this.count]=toast,this.scheduleRemoval(this.count),this.count++}else this.pendingToasts.push(toast)},scheduleRemoval:function(toastIndex){var _this3=this;Object.keys(this.pendingRemovals).includes(toastIndex.toString())||0!==this.toasts[toastIndex].duration&&(this.pendingRemovals[toastIndex]=setTimeout((function(){_this3.remove(toastIndex)}),this.toasts[toastIndex].duration))},scheduleRemovalWithOlder:function(){for(var toastIndex=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.count,i=0;i=toastIndex;i--)clearTimeout(this.pendingRemovals[i]),delete this.pendingRemovals[i]},remove:function(index){var _this4=this;this.toasts[index]&&(this.toasts[index].show=0),setTimeout((function(){_this4.toasts[index]="",delete _this4.pendingRemovals[index]}),500)}}}))}))}));
2 | //# sourceMappingURL=tall-toasts.js.map
3 |
--------------------------------------------------------------------------------
/dist/js/tall-toasts.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"tall-toasts.js","sources":["../../builds/cdn.js","../../resources/js/tall-toasts.js"],"sourcesContent":["import toast from '../resources/js/tall-toasts';\n\ndocument.addEventListener('alpine:initializing', () => {\n toast(window.Alpine);\n});\n","export default function (Alpine) {\n Alpine.data('ToastComponent',\n ($wire) => ({\n defaultDuration: $wire.defaultDuration,\n wireToasts: $wire.$entangle('toasts'),\n prod: $wire.$entangle('prod'),\n wireToastsIndex: 0,\n toasts: [],\n pendingToasts: [],\n pendingRemovals: [],\n count: 0,\n loaded: false,\n\n init () {\n window.Toast = {\n component: this,\n\n make: (message, title, type, duration) => ({ title, message, type, duration }),\n\n debug (message, title = '', duration = undefined) {\n this.component.add(this.make(message, title, 'debug', duration ?? this.component.defaultDuration));\n },\n\n info (message, title = '', duration = undefined) {\n this.component.add(this.make(message, title, 'info', duration ?? this.component.defaultDuration));\n },\n\n success (message, title = '', duration = undefined) {\n this.component.add(this.make(message, title, 'success', duration ?? this.component.defaultDuration));\n },\n\n warning (message, title = '', duration = undefined) {\n this.component.add(this.make(message, title, 'warning', duration ?? this.component.defaultDuration));\n },\n\n danger (message, title = '', duration = undefined) {\n this.component.add(this.make(message, title, 'danger', duration ?? this.component.defaultDuration));\n }\n };\n\n addEventListener('toast', (event) => {\n this.add(event.detail);\n });\n\n this.fetchWireToasts();\n\n this.$watch('wireToasts', () => {\n this.fetchWireToasts();\n });\n\n setTimeout(() => {\n this.loaded = true;\n this.pendingToasts.forEach((toast) => {\n this.add(toast);\n });\n this.pendingToasts = null;\n }, $wire.loadDelay);\n },\n\n fetchWireToasts () {\n this.wireToasts.forEach((toast, i) => {\n if (i < this.wireToastsIndex) {\n return;\n }\n\n this.add(window.Alpine.raw(toast));\n\n this.wireToastsIndex++;\n });\n },\n\n add (toast) {\n if (this.loaded !== true) {\n this.pendingToasts.push(toast);\n\n return;\n }\n\n if (toast.type === 'debug') {\n if (this.prod) {\n return;\n }\n\n console.log(toast.title, toast.message);\n }\n\n toast.type ??= 'info';\n toast.show = 0;\n toast.index = this.count;\n\n this.toasts[this.count] = toast;\n\n this.scheduleRemoval(this.count);\n\n this.count++;\n },\n\n scheduleRemoval (toastIndex) {\n if (Object.keys(this.pendingRemovals).includes(toastIndex.toString())) {\n return;\n }\n\n if (this.toasts[toastIndex].duration === 0) {\n return;\n }\n\n this.pendingRemovals[toastIndex] = setTimeout(() => {\n this.remove(toastIndex);\n }, this.toasts[toastIndex].duration);\n },\n\n scheduleRemovalWithOlder (toastIndex = this.count) {\n for (let i = 0; i < toastIndex; i++) {\n this.scheduleRemoval(i);\n }\n },\n\n cancelRemovalWithNewer (toastIndex) {\n for (let i = this.count - 1; i >= toastIndex; i--) {\n clearTimeout(this.pendingRemovals[i]);\n delete this.pendingRemovals[i];\n }\n },\n\n remove (index) {\n if (this.toasts[index]) {\n this.toasts[index].show = 0;\n }\n\n setTimeout(() => {\n this.toasts[index] = '';\n delete this.pendingRemovals[index];\n }, 500);\n }\n\n })\n );\n}\n"],"names":["document","addEventListener","window","Alpine","data","$wire","defaultDuration","wireToasts","$entangle","prod","wireToastsIndex","toasts","pendingToasts","pendingRemovals","count","loaded","init","_this","this","Toast","component","make","message","title","type","duration","debug","arguments","length","undefined","add","info","success","warning","danger","event","detail","fetchWireToasts","$watch","setTimeout","forEach","toast","loadDelay","_this2","i","raw","_toast$type","console","log","show","index","scheduleRemoval","push","toastIndex","_this3","Object","keys","includes","toString","remove","scheduleRemovalWithOlder","cancelRemovalWithNewer","clearTimeout","_this4"],"mappings":"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"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tall-toasts",
3 | "author": "John F (#usernotnull)",
4 | "license": "MIT",
5 | "scripts": {
6 | "build": "npx rollup -c",
7 | "watch": "npx rollup -c -w"
8 | },
9 | "devDependencies": {
10 | "@babel/core": "^7.27",
11 | "@babel/preset-env": "^7.27",
12 | "@babel/plugin-proposal-object-rest-spread": "^7.20",
13 | "@rollup/plugin-alias": "^5.1",
14 | "@rollup/plugin-node-resolve": "^15.3",
15 | "@rollup/plugin-alias": "^5.1",
16 | "@rollup/plugin-commonjs": "^25.0",
17 | "@rollup/plugin-babel": "^6.0",
18 | "core-js": "^3.42",
19 | "fs-extra": "^11.3",
20 | "get-value": "^3.0",
21 | "md5": "^2.3",
22 | "rollup": "^2.79",
23 | "rollup-plugin-filesize": "^10.0",
24 | "rollup-plugin-output-manifest": "^2.0",
25 | "rollup-plugin-terser": "^7.0",
26 | "whatwg-fetch": "^3.6"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | The Laravel Coding Standards
12 |
13 |
21 | src
22 | config
23 | tests
24 |
25 |
30 | */cache/*
31 | */*.js
32 | */*.css
33 | */*.xml
34 | */*.blade.php
35 | */autoload.php
36 | */vendor/*
37 | index.php
38 |
39 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/phpmd-ruleset.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | Inspired by https://github.com/phpmd/phpmd/issues/137
9 | using http://phpmd.org/documentation/creating-a-ruleset.html
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
47 | 3
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | - ./vendor/nunomaduro/larastan/extension.neon
3 |
4 | parameters:
5 | paths:
6 | - src
7 |
8 | # The level 9 is the highest level
9 | level: 5
10 |
11 | excludePaths:
12 | - ./src/ToastServiceProvider.php
13 |
14 | ignoreErrors:
15 | - '#Unsafe usage of new static#'
16 | - '#Call to an undefined method Illuminate\\Support\\HigherOrder#'
17 | - '#Call to an undefined static method#'
18 | - '#Parameter (\#)(\d) \$callback of method Illuminate\\Container\\Container::call\(\) expects \(callable\(\): (.*)#'
19 | - '#Parameter (\#)(\d) \$callback of function view expects view-string|null, string given#'
20 |
21 | reportUnmatchedIgnoredErrors: false
22 | checkOctaneCompatibility: true
23 |
--------------------------------------------------------------------------------
/resources/js/tall-toasts.js:
--------------------------------------------------------------------------------
1 | export default function (Alpine) {
2 | Alpine.data('ToastComponent',
3 | ($wire) => ({
4 | defaultDuration: $wire.defaultDuration,
5 | wireToasts: $wire.$entangle('toasts'),
6 | prod: $wire.$entangle('prod'),
7 | wireToastsIndex: 0,
8 | toasts: [],
9 | pendingToasts: [],
10 | pendingRemovals: [],
11 | count: 0,
12 | loaded: false,
13 |
14 | init () {
15 | window.Toast = {
16 | component: this,
17 |
18 | make: (message, title, type, duration) => ({ title, message, type, duration }),
19 |
20 | debug (message, title = '', duration = undefined) {
21 | this.component.add(this.make(message, title, 'debug', duration ?? this.component.defaultDuration));
22 | },
23 |
24 | info (message, title = '', duration = undefined) {
25 | this.component.add(this.make(message, title, 'info', duration ?? this.component.defaultDuration));
26 | },
27 |
28 | success (message, title = '', duration = undefined) {
29 | this.component.add(this.make(message, title, 'success', duration ?? this.component.defaultDuration));
30 | },
31 |
32 | warning (message, title = '', duration = undefined) {
33 | this.component.add(this.make(message, title, 'warning', duration ?? this.component.defaultDuration));
34 | },
35 |
36 | danger (message, title = '', duration = undefined) {
37 | this.component.add(this.make(message, title, 'danger', duration ?? this.component.defaultDuration));
38 | }
39 | };
40 |
41 | addEventListener('toast', (event) => {
42 | this.add(event.detail);
43 | });
44 |
45 | this.fetchWireToasts();
46 |
47 | this.$watch('wireToasts', () => {
48 | this.fetchWireToasts();
49 | });
50 |
51 | setTimeout(() => {
52 | this.loaded = true;
53 | this.pendingToasts.forEach((toast) => {
54 | this.add(toast);
55 | });
56 | this.pendingToasts = null;
57 | }, $wire.loadDelay);
58 | },
59 |
60 | fetchWireToasts () {
61 | this.wireToasts.forEach((toast, i) => {
62 | if (i < this.wireToastsIndex) {
63 | return;
64 | }
65 |
66 | this.add(window.Alpine.raw(toast));
67 |
68 | this.wireToastsIndex++;
69 | });
70 | },
71 |
72 | add (toast) {
73 | if (this.loaded !== true) {
74 | this.pendingToasts.push(toast);
75 |
76 | return;
77 | }
78 |
79 | if (toast.type === 'debug') {
80 | if (this.prod) {
81 | return;
82 | }
83 |
84 | console.log(toast.title, toast.message);
85 | }
86 |
87 | toast.type ??= 'info';
88 | toast.show = 0;
89 | toast.index = this.count;
90 |
91 | this.toasts[this.count] = toast;
92 |
93 | this.scheduleRemoval(this.count);
94 |
95 | this.count++;
96 | },
97 |
98 | scheduleRemoval (toastIndex) {
99 | if (Object.keys(this.pendingRemovals).includes(toastIndex.toString())) {
100 | return;
101 | }
102 |
103 | if (this.toasts[toastIndex].duration === 0) {
104 | return;
105 | }
106 |
107 | this.pendingRemovals[toastIndex] = setTimeout(() => {
108 | this.remove(toastIndex);
109 | }, this.toasts[toastIndex].duration);
110 | },
111 |
112 | scheduleRemovalWithOlder (toastIndex = this.count) {
113 | for (let i = 0; i < toastIndex; i++) {
114 | this.scheduleRemoval(i);
115 | }
116 | },
117 |
118 | cancelRemovalWithNewer (toastIndex) {
119 | for (let i = this.count - 1; i >= toastIndex; i--) {
120 | clearTimeout(this.pendingRemovals[i]);
121 | delete this.pendingRemovals[i];
122 | }
123 | },
124 |
125 | remove (index) {
126 | if (this.toasts[index]) {
127 | this.toasts[index].show = 0;
128 | }
129 |
130 | setTimeout(() => {
131 | this.toasts[index] = '';
132 | delete this.pendingRemovals[index];
133 | }, 500);
134 | }
135 |
136 | })
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/resources/views/includes/content.blade.php:
--------------------------------------------------------------------------------
1 |