├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dist └── js │ └── Bs5Utils.js ├── example ├── img │ ├── api-modal-1.png │ ├── api-modal-2.png │ ├── api-modal-3.png │ ├── api-modal-4.png │ ├── api-snacks.png │ ├── api-toasts.png │ ├── theming-modal.png │ ├── theming-snack.png │ └── theming-toast.png └── index.html ├── package-lock.json ├── package.json └── src └── js ├── Bs5Utils.js └── components ├── Modal.js ├── Snack.js └── Toast.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | - 3 | 4 | Use: [Babel Repl](https://babeljs.io/repl) to transpile the code and use [JavaScript Minifier](https://javascript-minifier.com/) to minify the transpiled code. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Script47 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bs5Utils - A JavaScript utility package for Bootstrap 5 components 2 | 3 | A simple package to make the usage of various components within Bootstrap 5 easier to use. 4 | 5 | If this package has helped you, and you're feeling particularly generous: 6 | - **ETH/MATIC:** 0x6515654c8e931052ab17a63311411D475D503e59 7 | - **ADA:** addr1qxaqvghsr8lu3wrmql4fcvg6txj5083s2a9rr5dmrrtjt0yn8t0x4yav3ma2flg3tzcu9767s7senydcumnf6c4krnnspn949q 8 | 9 | **Note:** The package is also available on npmjs: https://www.npmjs.com/package/bs5-utils 10 | 11 | --- 12 | 13 | Contents 14 | - 15 | 16 | - [Configuration](#configuration) 17 | - [Theming](#theming) 18 | - [API](#api) 19 | - [Support & Contribute](#support--contribute) 20 | 21 | Configuration 22 | - 23 | 24 | There are several defaults which you can customize: 25 | 26 | ```javascript 27 | Bs5Utils.defaults.toasts.position = 'top-right'; 28 | Bs5Utils.defaults.toasts.container = 'toast-container'; 29 | Bs5Utils.defaults.toasts.stacking = false; 30 | ``` 31 | 32 | As `bs5Utils.Snack` is a subset of `bs5Utils.Toast`, the configuration for toasts will also apply to `bs5Utils.Sanck`. 33 | 34 | Theming 35 | - 36 | 37 | You can register your own custom styles by passing classes to specific components by using the static 38 | method `Bs5Utils.registerStyle`. The components you can customise are: 39 | 40 | - `btnClose` - The dismiss button 41 | - `main` - The area of the toast, snack, or modal which will display the `type` color 42 | - `border` - The border of the component 43 | 44 | These components have been clearly illustrated below. For the time being, the `border` style for `bs5Utils.Snack` cannot 45 | be overridden. 46 | 47 | **Note:** All of these keys _must_ be passed in the `styles` parameter object. 48 | 49 | **Method Overview** 50 | 51 | ```javascript 52 | /** 53 | * Register a style for the components 54 | * @param key - To reference your style 55 | * @param styles - The style object 56 | */ 57 | Bs5Utils.registerStyle(key, styles) 58 | ``` 59 | 60 | **Usage** 61 | 62 | You first define your CSS classes: 63 | 64 | ```css 65 | .bg-pink { 66 | background-color: pink; 67 | } 68 | 69 | .text-purple { 70 | color: purple; 71 | } 72 | 73 | .border-pink { 74 | border-color: pink !important; 75 | } 76 | ``` 77 | 78 | Then you register the style: 79 | 80 | ```javascript 81 | Bs5Utils.registerStyle('pink', { 82 | btnClose: ['btn-close-white'], 83 | main: ['bg-pink', 'text-purple'], 84 | border: ['border-pink'] 85 | }); 86 | ``` 87 | 88 | Pass empty arrays if you wish to leave the default styles e.g. 89 | 90 | ```javascript 91 | Bs5Utils.registerStyle('pink', { 92 | btnClose: [], 93 | main: ['bg-pink', 'text-purple'], 94 | border: ['border-pink'] 95 | }); 96 | ``` 97 | 98 | Now, `pink` can be used as a `type` when displaying snacks, toasts, or modals e.g. 99 | 100 | **Snack** 101 | 102 | ![Theming Snack](example/img/theming-snack.png) 103 | 104 | **Toast** 105 | 106 | ![Theming Snack](example/img/theming-toast.png) 107 | 108 | **Modal** 109 | 110 | ![Theming Modal](example/img/theming-modal.png) 111 | 112 | API 113 | - 114 | 115 | This package is based around the `Bs5Utils` class, so first things first, construct the object: 116 | 117 | ```javascript 118 | const bs5Utils = new Bs5Utils(); 119 | ``` 120 | 121 | Thereafter you'll be able to use the methods outlined below. 122 | 123 | ### Snacks 124 | 125 | **Method Overview** 126 | 127 | ```javascript 128 | /** 129 | * Display a lightweight toast for simple alerts 130 | * @param - type the theme of the snack 131 | * @param - title the title of the of the snack 132 | * @param - delay in ms, if specified the snack will autohide after the specified amount 133 | * @param - dismissible set whether the dismiss button should show 134 | */ 135 | bs5Utils.Snack.show( 136 | type, 137 | title, 138 | delay = 0, 139 | dismissible = true 140 | ); 141 | ``` 142 | 143 | **Usage** 144 | 145 | ```javascript 146 | bs5Utils.Snack.show('secondary', 'Hello World!', delay = 0, dismissible = true); 147 | bs5Utils.Snack.show('light', 'Hello World!', delay = 0, dismissible = true); 148 | bs5Utils.Snack.show('white', 'Hello World!', delay = 0, dismissible = true); 149 | bs5Utils.Snack.show('dark', 'Hello World!', delay = 0, dismissible = true); 150 | bs5Utils.Snack.show('info', 'Hello World!', delay = 0, dismissible = true); 151 | bs5Utils.Snack.show('primary', 'Hello World!', delay = 0, dismissible = true); 152 | bs5Utils.Snack.show('success', 'Hello World!', delay = 0, dismissible = true); 153 | bs5Utils.Snack.show('warning', 'Hello World!', delay = 0, dismissible = true); 154 | bs5Utils.Snack.show('danger', 'Hello World!', delay = 0, dismissible = true); 155 | ``` 156 | 157 | **Example** 158 | 159 | ![img.png](example/img/api-snacks.png) 160 | 161 | ### Toasts 162 | 163 | **Method Overview** 164 | 165 | ```javascript 166 | /** 167 | * Display a toast for alerts 168 | * @param type - the theme of the snack 169 | * @param icon - Set an icon in the top-left corner, you can pass HTML directly 170 | * @param title - the title of the of the toast 171 | * @param subtitle - the subtitle of the toast 172 | * @param content - the content of the toast 173 | * @param buttons - the action buttons of the toast 174 | * @param delay - in ms, if specified the snack will autohide after the specified amount 175 | * @param dismissible - set whether the dismiss button should show 176 | */ 177 | bs5Utils.Toast.show({ 178 | type, 179 | icon = '', 180 | title, 181 | subtitle = '', 182 | content = '', 183 | buttons = [], 184 | delay = 0, 185 | dismissible = true, 186 | }); 187 | ``` 188 | 189 | **Usage** 190 | 191 | ```javascript 192 | bs5Utils.Toast.show({ 193 | type: 'primary', 194 | icon: ``, 195 | title: 'Notification!', 196 | subtitle: '23 secs ago', 197 | content: 'Hello World!', 198 | buttons: [ 199 | { 200 | text: 'Click Me!', 201 | class: 'btn btn-sm btn-primary', 202 | handler: () => { 203 | alert(`Button #1 has been clicked!`); 204 | } 205 | }, 206 | { 207 | text: 'Click Me Too!', 208 | class: 'btn btn-sm btn-warning', 209 | handler: () => { 210 | alert(`You clicked me too!`); 211 | } 212 | }, 213 | { 214 | type: 'dismiss', 215 | text: 'Hide', 216 | class: 'btn btn-sm btn-secondary' 217 | } 218 | ], 219 | delay: 0, 220 | dismissible: true 221 | }); 222 | ``` 223 | 224 | **Example** 225 | 226 | ![img.png](example/img/api-toasts.png) 227 | 228 | ### Modals 229 | 230 | **Method Overview** 231 | 232 | ```javascript 233 | /** 234 | * Display a modal 235 | * @param type - the theme of the snack 236 | * @param title - the title of the modal, if omitted, the modal-header element is removed 237 | * @param content - the content of the modal, if omitted, the modal-body element is removed 238 | * @param buttons - any action buttons, if omitted, the the modal-footer element is removed 239 | * @param centered - set whether the modal is centered 240 | * @param dismissible - set whether the dismiss button should show 241 | * @param backdrop - set the type of backdrop: true, false, static 242 | * @param keyboard - set whether the escape key closes the modal 243 | * @param focus - set whether the modal is autofocussed when initialized 244 | * @param fullscreen - set whether the modal is fullscreen 245 | * @param modalSize - set the size of the modal: sm, lg, xl by default, it's an empty string 246 | */ 247 | bs5Utils.Modal.show({ 248 | type, 249 | title = '', 250 | content = '', 251 | buttons = [], 252 | centered = false, 253 | dismissible = true, 254 | backdrop = dismissible ? true : 'static', 255 | keyboard = dismissible, 256 | focus = true, 257 | fullscreen = false, 258 | size = '' 259 | }) 260 | ``` 261 | 262 | **Usage** 263 | 264 | ```javascript 265 | bs5Utils.Modal.show({ 266 | type: 'primary', 267 | title: `Hello World!`, 268 | content: `

Hello World!

`, 269 | buttons: [ 270 | { 271 | text: 'Click Me!', 272 | class: 'btn btn-sm btn-primary', 273 | handler: () => { 274 | alert(`Button #1 has been clicked!`); 275 | } 276 | }, 277 | { 278 | text: 'Click Me Too!', 279 | class: 'btn btn-sm btn-warning', 280 | handler: () => { 281 | alert(`You clicked me too!`); 282 | } 283 | }, 284 | { 285 | type: 'dismiss', 286 | text: 'Hide', 287 | class: 'btn btn-sm btn-secondary' 288 | } 289 | ], 290 | centered: true, 291 | dismissible: true, 292 | backdrop: 'static', 293 | keyboard: false, 294 | focus: false 295 | }); 296 | ``` 297 | 298 | **Example** 299 | 300 | ![img.png](example/img/api-modal-1.png) 301 | 302 | ![img.png](example/img/api-modal-2.png) 303 | 304 | ![img_1.png](example/img/api-modal-3.png) 305 | 306 | ![img.png](example/img/api-modal-4.png) 307 | 308 | Support & Contribute 309 | - 310 | 311 | - Use: [Babel Repl](https://babeljs.io/repl) and [JavaScript Minifier](https://javascript-minifier.com/) to build the 312 | app to transpile and minify your changes 313 | - Submit issues and PRs 314 | - Let's know how you're using this package in your project 315 | - If this package has helped you, and you're feeling particularly generous: 316 | - **ETH/MATIC:** 0x6515654c8e931052ab17a63311411D475D503e59 317 | - **ADA:** addr1qxaqvghsr8lu3wrmql4fcvg6txj5083s2a9rr5dmrrtjt0yn8t0x4yav3ma2flg3tzcu9767s7senydcumnf6c4krnnspn949q 318 | -------------------------------------------------------------------------------- /dist/js/Bs5Utils.js: -------------------------------------------------------------------------------- 1 | typeof exports === 'undefined' ? exports = {} : null; "use strict";function _classPrivateMethodInitSpec(t,e){_checkPrivateRedeclaration(t,e),e.add(t)}function _defineProperty(t,e,s){return e in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}function _classPrivateMethodGet(t,e,s){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return s}function _classPrivateFieldInitSpec(t,e,s){_checkPrivateRedeclaration(t,e),e.set(t,s)}function _checkPrivateRedeclaration(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function _classPrivateFieldSet(t,e,s){return _classApplyDescriptorSet(t,_classExtractFieldDescriptor(t,e,"set"),s),s}function _classApplyDescriptorSet(t,e,s){if(e.set)e.set.call(t,s);else{if(!e.writable)throw new TypeError("attempted to set read only private field");e.value=s}}function _classPrivateFieldGet(t,e){return _classApplyDescriptorGet(t,_classExtractFieldDescriptor(t,e,"get"))}function _classExtractFieldDescriptor(t,e,s){if(!e.has(t))throw new TypeError("attempted to "+s+" private field on non-instance");return e.get(t)}function _classApplyDescriptorGet(t,e){return e.get?e.get.call(t):e.value}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _count=new WeakMap;class Toast{constructor(){_classPrivateFieldInitSpec(this,_count,{writable:!0,value:0})}show({type:t,icon:e="",title:s,subtitle:a="",content:o="",buttons:i=[],delay:n=0,dismissible:r=!0}){_classPrivateFieldSet(this,_count,1+ +_classPrivateFieldGet(this,_count));const l=Bs5Utils.defaults.styles[t],d=l.btnClose.join(" "),c=l.border,b=document.createElement("div");b.setAttribute("id",`toast-${_classPrivateFieldGet(this,_count)}`),b.setAttribute("role","alert"),b.setAttribute("aria-live","assertive"),b.setAttribute("aria-atomic","true"),b.classList.add("toast","align-items-center"),c.forEach(t=>{b.classList.add(t)});let u="",m=[];Array.isArray(i)&&i.length&&(u+=`
`,i.forEach((t,e)=>{switch(t.type||"button"){case"dismiss":u+=` `;break;default:let s=`toast-${_classPrivateFieldGet(this,_count)}-button-${e}`;u+=` `,t.hasOwnProperty("handler")&&"function"==typeof t.handler&&m.push({id:s,handler:t.handler})}}),u+="
"),b.innerHTML=`
\n ${e}\n ${s}\n ${a}\n ${r?``:""}\n
\n
\n ${o}\n ${u}\n
`,Bs5Utils.defaults.toasts.stacking||document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach(t=>{t.remove()}),document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(b),b.addEventListener("hidden.bs.toast",function(t){t.target.remove()}),m.forEach(t=>{document.getElementById(t.id).addEventListener("click",t.handler)});const h={autohide:n>0&&"number"==typeof n};n>0&&"number"==typeof n&&(h.delay=n);const p=new bootstrap.Toast(b,h);return p.show(),p}}var _count2=new WeakMap;class Snack{constructor(){_classPrivateFieldInitSpec(this,_count2,{writable:!0,value:0})}show(t,e,s=0,a=!0){_classPrivateFieldSet(this,_count2,1+ +_classPrivateFieldGet(this,_count2));const o=Bs5Utils.defaults.styles[t],i=o.btnClose.join(" "),n=document.createElement("div");n.classList.add("toast","align-items-center","border-1","border-dark"),o.main.forEach(t=>{n.classList.add(t)}),n.setAttribute("id",`snack-${_classPrivateFieldGet(this,_count2)}`),n.setAttribute("role","alert"),n.setAttribute("aria-live","assertive"),n.setAttribute("aria-atomic","true"),n.innerHTML=`
\n
${e}
\n ${a?``:""}\n
`,Bs5Utils.defaults.toasts.stacking||document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach(t=>{t.remove()}),document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(n),n.addEventListener("hidden.bs.toast",function(t){t.target.remove()});const r={autohide:s>0&&"number"==typeof s};s>0&&"number"==typeof s&&(r.delay=s);const l=new bootstrap.Toast(n,r);return l.show(),l}}var _count3=new WeakMap;class Modal{constructor(){_classPrivateFieldInitSpec(this,_count3,{writable:!0,value:0})}show({type:t,title:e="",content:s="",buttons:a=[],centered:o=!1,dismissible:i=!0,backdrop:n=!!i||"static",keyboard:r=i,focus:l=!0,fullscreen:d=!1,size:c=""}){_classPrivateFieldSet(this,_count3,1+ +_classPrivateFieldGet(this,_count3)),c=["sm","lg","xl"].includes(c)?`modal-${c}`:"",d=d?"modal-fullscreen":"",o=o?"modal-dialog-centered modal-dialog-scrollable":"";const b=Bs5Utils.defaults.styles[t],u=b.btnClose.join(" "),m=b.border,h=document.createElement("div");h.setAttribute("id",`modal-${_classPrivateFieldGet(this,_count3)}`),h.setAttribute("tabindex","-1"),h.classList.add("modal");let p="",v=[];Array.isArray(a)&&a.length&&(p+=`"),h.innerHTML=` `,document.body.appendChild(h),h.addEventListener("hidden.bs.modal",function(t){t.target.remove()}),v.forEach(t=>{document.getElementById(t.id).addEventListener("click",t.handler)});const f={backdrop:n,keyboard:r,focus:l},y=new bootstrap.Modal(h,f);return y.show(),y}}var _createToastContainer=new WeakSet;class Bs5Utils{constructor(){_classPrivateMethodInitSpec(this,_createToastContainer),_classPrivateMethodGet(this,_createToastContainer,_createToastContainer2).call(this),this.Toast=new Toast,this.Snack=new Snack,this.Modal=new Modal}static registerStyle(t,e){if("object"!=typeof e&&Array.isArray(e))throw"The styles parameter must be an object when you register component style.";Bs5Utils.defaults.styles[t]=e}}function _createToastContainer2(){let t=document.querySelector(`#${Bs5Utils.defaults.toasts.container}`);if(!t){const e={"top-left":"top-0 start-0 ms-1 mt-1","top-center":"top-0 start-50 translate-middle-x mt-1","top-right":"top-0 end-0 me-1 mt-1","middle-left":"top-50 start-0 translate-middle-y ms-1","middle-center":"top-50 start-50 translate-middle p-3","middle-right":"top-50 end-0 translate-middle-y me-1","bottom-left":"bottom-0 start-0 ms-1 mb-1","bottom-center":"bottom-0 start-50 translate-middle-x mb-1","bottom-right":"bottom-0 end-0 me-1 mb-1"};(t=document.createElement("div")).classList.add("position-relative"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-atomic","true"),t.innerHTML=`
`,document.body.appendChild(t)}}exports.default=Bs5Utils,_defineProperty(Bs5Utils,"defaults",{toasts:{position:"top-right",container:"toast-container",stacking:!0},styles:{secondary:{btnClose:["btn-close-white"],main:["text-white","bg-secondary"],border:["border-secondary"]},light:{btnClose:[],main:["text-dark","bg-light","border-bottom","border-dark"],border:["border-dark"]},white:{btnClose:[],main:["text-dark","bg-white","border-bottom","border-dark"],border:["border-dark"]},dark:{btnClose:["btn-close-white"],main:["text-white","bg-dark"],border:["border-dark"]},info:{btnClose:["btn-close-white"],main:["text-white","bg-info"],border:["border-info"]},primary:{btnClose:["btn-close-white"],main:["text-white","bg-primary"],border:["border-primary"]},success:{btnClose:["btn-close-white"],main:["text-white","bg-success"],border:["border-success"]},warning:{btnClose:["btn-close-white"],main:["text-white","bg-warning"],border:["border-warning"]},danger:{btnClose:["btn-close-white"],main:["text-white","bg-danger"],border:["border-danger"]}}}); -------------------------------------------------------------------------------- /example/img/api-modal-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-modal-1.png -------------------------------------------------------------------------------- /example/img/api-modal-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-modal-2.png -------------------------------------------------------------------------------- /example/img/api-modal-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-modal-3.png -------------------------------------------------------------------------------- /example/img/api-modal-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-modal-4.png -------------------------------------------------------------------------------- /example/img/api-snacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-snacks.png -------------------------------------------------------------------------------- /example/img/api-toasts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/api-toasts.png -------------------------------------------------------------------------------- /example/img/theming-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/theming-modal.png -------------------------------------------------------------------------------- /example/img/theming-snack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/theming-snack.png -------------------------------------------------------------------------------- /example/img/theming-toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Script47/bs5-utils/3ce50a21f1224279d0799905f73993e3d66bd3d6/example/img/theming-toast.png -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 12 | 16 | 29 | 30 | Hello, world! 31 | 32 | 33 | 34 |
35 |

bs5-utils

36 |
37 | 38 |
39 |
40 |
41 |
42 |
Bs5Utils.Snack.show
43 |
44 |
bs5Utils.Snack.show(
 45 |     type,
 46 |     title,
 47 |     delay = 0,
 48 |     dismissible = true
 49 | );
50 |
51 | 54 |
55 | 56 |
57 |
Bs5Utils.Toast.show
58 |
59 |
bs5Utils.Toast.show({
 60 |      type,
 61 |      icon = '',
 62 |      title,
 63 |      subtitle = '',
 64 |      content = '',
 65 |      buttons = [],
 66 |      delay = 0,
 67 |      dismissible = true
 68 | });
69 |
70 | 73 |
74 | 75 |
76 |
Bs5Utils.Modal.show
77 |
78 |
bs5Utils.Modal.show({
 79 |      type,
 80 |      title = '',
 81 |      content = '',
 82 |      buttons = [],
 83 |      centered = false,
 84 |      dismissible = true,
 85 |      backdrop = dismissible ? true : 'static',
 86 |      keyboard = dismissible,
 87 |      focus = true,
 88 |      fullscreen = false,
 89 |      size = ''
 90 | });
91 |
92 | 95 |
96 |
97 |
98 |
99 | 100 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bs5-utils", 3 | "version": "1.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bootstrap": { 8 | "version": "5.1.0", 9 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", 10 | "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bs5-utils", 3 | "version": "1.0.3", 4 | "description": "A JavaScript utility package for Bootstrap 5 components.", 5 | "main": "src/js/Bs5Utils.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/Script47/bs5-utils.git" 9 | }, 10 | "keywords": [ 11 | "bootstrap-5", 12 | "bootstrap" 13 | ], 14 | "author": "Script47 ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/Script47/bs5-utils/issues" 18 | }, 19 | "homepage": "https://github.com/Script47/bs5-utils", 20 | "scripts": {}, 21 | "dependencies": { 22 | "bootstrap": ">=5.0" 23 | }, 24 | "devDependencies": {} 25 | } 26 | -------------------------------------------------------------------------------- /src/js/Bs5Utils.js: -------------------------------------------------------------------------------- 1 | import Toast from "./components/Toast"; 2 | import Snack from "./components/Snack"; 3 | import Modal from "./components/Modal"; 4 | 5 | class Bs5Utils { 6 | /** 7 | * Default config options 8 | * @type {{toasts: {container: string, position: string, stacking: boolean}}} 9 | */ 10 | static defaults = { 11 | toasts: { 12 | position: 'top-right', 13 | container: 'toast-container', 14 | stacking: true 15 | }, 16 | 17 | styles: { 18 | secondary: { 19 | btnClose: ['btn-close-white'], 20 | main: ['text-white', 'bg-secondary'], 21 | border: ['border-secondary'] 22 | }, 23 | light: { 24 | btnClose: [], 25 | main: ['text-dark', 'bg-light', 'border-bottom', 'border-dark'], 26 | border: ['border-dark'] 27 | }, 28 | white: { 29 | btnClose: [], 30 | main: ['text-dark', 'bg-white', 'border-bottom', 'border-dark'], 31 | border: ['border-dark'] 32 | }, 33 | dark: { 34 | btnClose: ['btn-close-white'], 35 | main: ['text-white', 'bg-dark'], 36 | border: ['border-dark'] 37 | }, 38 | info: { 39 | btnClose: ['btn-close-white'], 40 | main: ['text-white', 'bg-info'], 41 | border: ['border-info'] 42 | }, 43 | primary: { 44 | btnClose: ['btn-close-white'], 45 | main: ['text-white', 'bg-primary'], 46 | border: ['border-primary'] 47 | }, 48 | success: { 49 | btnClose: ['btn-close-white'], 50 | main: ['text-white', 'bg-success'], 51 | border: ['border-success'] 52 | }, 53 | warning: { 54 | btnClose: ['btn-close-white'], 55 | main: ['text-white', 'bg-warning'], 56 | border: ['border-warning'] 57 | }, 58 | danger: { 59 | btnClose: ['btn-close-white'], 60 | main: ['text-white', 'bg-danger'], 61 | border: ['border-danger'] 62 | } 63 | } 64 | } 65 | 66 | constructor() { 67 | this.#createToastContainer(); 68 | 69 | this.Toast = new Toast(); 70 | this.Snack = new Snack(); 71 | this.Modal = new Modal(); 72 | } 73 | 74 | #createToastContainer() { 75 | let containerEl = document.querySelector(`#${Bs5Utils.defaults.toasts.container}`); 76 | 77 | if (!containerEl) { 78 | const positionToClass = { 79 | 'top-left': 'top-0 start-0 ms-1 mt-1', 80 | 'top-center': 'top-0 start-50 translate-middle-x mt-1', 81 | 'top-right': 'top-0 end-0 me-1 mt-1', 82 | 'middle-left': 'top-50 start-0 translate-middle-y ms-1', 83 | 'middle-center': 'top-50 start-50 translate-middle p-3', 84 | 'middle-right': 'top-50 end-0 translate-middle-y me-1', 85 | 'bottom-left': 'bottom-0 start-0 ms-1 mb-1', 86 | 'bottom-center': 'bottom-0 start-50 translate-middle-x mb-1', 87 | 'bottom-right': 'bottom-0 end-0 me-1 mb-1' 88 | }; 89 | 90 | containerEl = document.createElement('div'); 91 | containerEl.classList.add('position-relative'); 92 | containerEl.setAttribute('aria-live', 'polite'); 93 | containerEl.setAttribute('aria-atomic', 'true'); 94 | containerEl.innerHTML = `
`; 95 | 96 | document.body.appendChild(containerEl); 97 | } 98 | } 99 | 100 | /** 101 | * Register a style for the components 102 | * @param key - To reference your style 103 | * @param styles - The style object 104 | */ 105 | static registerStyle(key, styles) { 106 | if (typeof styles !== 'object' && Array.isArray(styles)) { 107 | throw 'The styles parameter must be an object when you register component style.' 108 | } 109 | 110 | Bs5Utils.defaults.styles[key] = styles; 111 | } 112 | } 113 | 114 | export { 115 | Bs5Utils as default 116 | }; -------------------------------------------------------------------------------- /src/js/components/Modal.js: -------------------------------------------------------------------------------- 1 | import Bs5Utils from "../Bs5Utils"; 2 | 3 | export default class Modal { 4 | /** 5 | * A counter for the Modals 6 | * @type {number} 7 | */ 8 | #count = 0; 9 | 10 | /** 11 | * Display a modal 12 | * @param type - the theme of the snack 13 | * @param title - the title of the modal, if omitted, the modal-header element is removed 14 | * @param content - the content of the modal, if omitted, the modal-body element is removed 15 | * @param buttons - any action buttons, if omitted, the the modal-footer element is removed 16 | * @param centered - set whether the modal is centered 17 | * @param dismissible - set whether the dismiss button should show 18 | * @param backdrop - set the type of backdrop: true, false, static 19 | * @param keyboard - set whether the escape key closes the modal 20 | * @param focus - set whether the modal is autofocussed when initialized 21 | * @param fullscreen - set whether the modal is fullscreen 22 | * @param modalSize - set the size of the modal: sm, lg, xl by default, it's an empty string 23 | */ 24 | show({ 25 | type, 26 | title = '', 27 | content = '', 28 | buttons = [], 29 | centered = false, 30 | dismissible = true, 31 | backdrop = dismissible ? true : 'static', 32 | keyboard = dismissible, 33 | focus = true, 34 | fullscreen = false, 35 | size = '' 36 | }) { 37 | this.#count++; 38 | 39 | size = ['sm', 'lg', 'xl'].includes(size) ? `modal-${size}` : ''; 40 | fullscreen = fullscreen ? 'modal-fullscreen' : ''; 41 | centered = centered ? 'modal-dialog-centered modal-dialog-scrollable' : ''; 42 | 43 | const style = Bs5Utils.defaults.styles[type], 44 | btnCloseStyles = style.btnClose.join(' '), 45 | borderStyles = style.border, 46 | modal = document.createElement('div'); 47 | 48 | modal.setAttribute('id', `modal-${this.#count}`) 49 | modal.setAttribute('tabindex', '-1'); 50 | modal.classList.add('modal'); 51 | 52 | let footerHtml = '', 53 | buttonIds = []; 54 | 55 | if (Array.isArray(buttons) && buttons.length) { 56 | footerHtml += ``; 81 | } 82 | 83 | modal.innerHTML = ` `; 93 | 94 | document.body.appendChild(modal); 95 | 96 | modal.addEventListener('hidden.bs.modal', function (e) { 97 | e.target.remove(); 98 | }); 99 | 100 | buttonIds.forEach(value => { 101 | document.getElementById(value.id).addEventListener('click', value.handler) 102 | }); 103 | 104 | const opts = { 105 | backdrop, 106 | keyboard, 107 | focus 108 | }; 109 | 110 | const bsModal = new bootstrap.Modal(modal, opts); 111 | 112 | bsModal.show(); 113 | 114 | return bsModal; 115 | } 116 | } -------------------------------------------------------------------------------- /src/js/components/Snack.js: -------------------------------------------------------------------------------- 1 | import Bs5Utils from "../Bs5Utils"; 2 | 3 | export default class Snack { 4 | /** 5 | * A counter for the Snacks 6 | * @type {number} 7 | */ 8 | #count = 0; 9 | 10 | /** 11 | * Display a lightweight toast 12 | * @param type - the theme of the snack 13 | * @param title - the title of the of the snack 14 | * @param delay - in ms, if specified the snack will autohide after the specified amount 15 | * @param dismissible - set whether the dismiss button should show 16 | */ 17 | show(type, title, delay = 0, dismissible = true) { 18 | this.#count++; 19 | 20 | const style = Bs5Utils.defaults.styles[type], 21 | btnCloseStyle = style.btnClose.join(' '), 22 | snack = document.createElement('div'); 23 | 24 | snack.classList.add('toast', 'align-items-center', 'border-1', 'border-dark'); 25 | style.main.forEach(value => { 26 | snack.classList.add(value); 27 | }); 28 | snack.setAttribute('id', `snack-${this.#count}`); 29 | snack.setAttribute('role', 'alert'); 30 | snack.setAttribute('aria-live', 'assertive'); 31 | snack.setAttribute('aria-atomic', 'true'); 32 | snack.innerHTML = `
33 |
${title}
34 | ${dismissible ? `` : ''} 35 |
`; 36 | 37 | if (!Bs5Utils.defaults.toasts.stacking) { 38 | document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach((toast) => { 39 | toast.remove(); 40 | }); 41 | } 42 | 43 | document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(snack); 44 | 45 | snack.addEventListener('hidden.bs.toast', function (e) { 46 | e.target.remove(); 47 | }); 48 | 49 | const opts = { 50 | autohide: (delay > 0 && typeof delay === 'number'), 51 | }; 52 | 53 | if (delay > 0 && typeof delay === 'number') { 54 | opts['delay'] = delay; 55 | } 56 | 57 | const bsSnack = new bootstrap.Toast(snack, opts); 58 | 59 | bsSnack.show(); 60 | 61 | return bsSnack; 62 | } 63 | } -------------------------------------------------------------------------------- /src/js/components/Toast.js: -------------------------------------------------------------------------------- 1 | import Bs5Utils from "../Bs5Utils"; 2 | 3 | export default class Toast { 4 | /** 5 | * A counter for the Toasts 6 | * @type {number} 7 | */ 8 | #count = 0; 9 | 10 | /** 11 | * Display a toast for alerts 12 | * @param type - the theme of the snack 13 | * @param icon - Set an icon in the top-left corner, you can pass HTML directly 14 | * @param title - the title of the of the toast 15 | * @param subtitle - the subtitle of the toast 16 | * @param content - the content of the toast 17 | * @param buttons - the action buttons of the toast 18 | * @param delay - in ms, if specified the snack will autohide after the specified amount 19 | * @param dismissible - set whether the dismiss button should show 20 | */ 21 | show({ 22 | type, 23 | icon = '', 24 | title, 25 | subtitle = '', 26 | content = '', 27 | buttons = [], 28 | delay = 0, 29 | dismissible = true 30 | }) { 31 | this.#count++; 32 | 33 | const style = Bs5Utils.defaults.styles[type], 34 | btnCloseStyles = style.btnClose.join(' '), 35 | borderStyles = style.border, 36 | toast = document.createElement('div'); 37 | 38 | toast.setAttribute('id', `toast-${this.#count}`); 39 | toast.setAttribute('role', 'alert'); 40 | toast.setAttribute('aria-live', 'assertive'); 41 | toast.setAttribute('aria-atomic', 'true'); 42 | 43 | toast.classList.add('toast', 'align-items-center'); 44 | borderStyles.forEach(value => { 45 | toast.classList.add(value); 46 | }); 47 | 48 | let buttonsHtml = ``, 49 | buttonIds = []; 50 | 51 | if (Array.isArray(buttons) && buttons.length) { 52 | buttonsHtml += `
`; 53 | 54 | buttons.forEach((button, key) => { 55 | const type = button.type || 'button'; 56 | 57 | switch (type) { 58 | case 'dismiss': 59 | buttonsHtml += ` `; 60 | break; 61 | 62 | default: 63 | let id = `toast-${this.#count}-button-${key}`; 64 | 65 | buttonsHtml += ` `; 66 | 67 | if (button.hasOwnProperty('handler') && typeof button.handler === 'function') { 68 | buttonIds.push({ 69 | id, 70 | handler: button.handler 71 | }); 72 | } 73 | } 74 | }); 75 | 76 | buttonsHtml += `
`; 77 | } 78 | 79 | toast.innerHTML = `
80 | ${icon} 81 | ${title} 82 | ${subtitle} 83 | ${dismissible ? `` : ''} 84 |
85 |
86 | ${content} 87 | ${buttonsHtml} 88 |
`; 89 | 90 | if (!Bs5Utils.defaults.toasts.stacking) { 91 | document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach((toast) => { 92 | toast.remove(); 93 | }); 94 | } 95 | 96 | document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(toast); 97 | 98 | toast.addEventListener('hidden.bs.toast', function (e) { 99 | e.target.remove(); 100 | }); 101 | 102 | buttonIds.forEach(value => { 103 | document.getElementById(value.id).addEventListener('click', value.handler) 104 | }); 105 | 106 | const opts = { 107 | autohide: (delay > 0 && typeof delay === 'number'), 108 | }; 109 | 110 | if (delay > 0 && typeof delay === 'number') { 111 | opts['delay'] = delay; 112 | } 113 | 114 | const bsToast = new bootstrap.Toast(toast, opts); 115 | 116 | bsToast.show(); 117 | 118 | return bsToast; 119 | } 120 | } --------------------------------------------------------------------------------