├── .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 | 
103 |
104 | **Toast**
105 |
106 | 
107 |
108 | **Modal**
109 |
110 | 
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 | 
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 | 
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 | 
301 |
302 | 
303 |
304 | 
305 |
306 | 
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=``,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=` \n
\n ${e.length?`
\n
${e}
\n ${i?``:""}\n `:""}\n ${s.length?`
${s}
`:""}\n ${p}\n
\n
`,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 |
43 |
44 |
bs5Utils.Snack.show(
45 | type,
46 | title,
47 | delay = 0,
48 | dismissible = true
49 | );
50 |
51 |
54 |
55 |
56 |
57 |
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 |
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 = `
84 |
85 | ${title.length ? `
86 |
${title}
87 | ${dismissible ? `` : ``}
88 | ` : ``}
89 | ${content.length ? `
${content}
` : ``}
90 | ${footerHtml}
91 |
92 |
`;
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 | }
--------------------------------------------------------------------------------