From 4ee933da0c62d3fd399e240f640d0a422749dba8 Mon Sep 17 00:00:00 2001 From: Script47 Date: Sun, 5 Sep 2021 19:51:43 +0100 Subject: [PATCH] V1.0.0 (#1) * v1.0.0 - initial commit * Update README.md --- .gitignore | 2 + README.md | 318 +++++++++++++++++++++++++++++++++- dist/js/Bs5Utils.js | 1 + example/img/api-modal-1.png | Bin 0 -> 5490 bytes example/img/api-modal-2.png | Bin 0 -> 4177 bytes example/img/api-modal-3.png | Bin 0 -> 2151 bytes example/img/api-modal-4.png | Bin 0 -> 3428 bytes example/img/api-snacks.png | Bin 0 -> 15748 bytes example/img/api-toasts.png | Bin 0 -> 5078 bytes example/img/theming-modal.png | Bin 0 -> 6523 bytes example/img/theming-snack.png | Bin 0 -> 4703 bytes example/img/theming-toast.png | Bin 0 -> 8064 bytes example/index.html | 284 ++++++++++++++++++++++++++++++ package-lock.json | 13 ++ package.json | 27 +++ src/js/Bs5Utils.js | 108 ++++++++++++ src/js/components/Modal.js | 114 ++++++++++++ src/js/components/Snack.js | 61 +++++++ src/js/components/Toast.js | 118 +++++++++++++ 19 files changed, 1045 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 dist/js/Bs5Utils.js create mode 100644 example/img/api-modal-1.png create mode 100644 example/img/api-modal-2.png create mode 100644 example/img/api-modal-3.png create mode 100644 example/img/api-modal-4.png create mode 100644 example/img/api-snacks.png create mode 100644 example/img/api-toasts.png create mode 100644 example/img/theming-modal.png create mode 100644 example/img/theming-snack.png create mode 100644 example/img/theming-toast.png create mode 100644 example/index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/js/Bs5Utils.js create mode 100644 src/js/components/Modal.js create mode 100644 src/js/components/Snack.js create mode 100644 src/js/components/Toast.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d2b47d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +node_modules \ No newline at end of file diff --git a/README.md b/README.md index 214294d..5311f34 100644 --- a/README.md +++ b/README.md @@ -1 +1,317 @@ -# bs5-utils \ No newline at end of file +# Bs5Utils - A JavaScript utility package for Bootstrap 5 components + +--- + +A simple package to make the usage of various components within Bootstrap 5 easier to use. + +If this package has helped you, and you're feeling particularly generous: +- **ETH/MATIC:** 0x6515654c8e931052ab17a63311411D475D503e59 +- **ADA:** addr1qxaqvghsr8lu3wrmql4fcvg6txj5083s2a9rr5dmrrtjt0yn8t0x4yav3ma2flg3tzcu9767s7senydcumnf6c4krnnspn949q + +--- + +Contents +- + +- [Configuration](#configuration) +- [Theming](#theming) +- [API](#api) +- [Support & Contribute](#support--contribute) + +Configuration +- + +There are several defaults which you can customize: + +```javascript +Bs5Utils.defaults.toasts.position = 'top-right'; +Bs5Utils.defaults.toasts.container = 'toast-container'; +Bs5Utils.defaults.toasts.stacking = false; +``` + +As `bs5Utils.Snack` is a subset of `bs5Utils.Toast`, the configuration for toasts will also apply to `bs5Utils.Sanck`. + +Theming +- + +You can register your own custom styles by passing classes to specific components by using the static +method `Bs5Utils.registerStyle`. The components you can customise are: + +- `btnClose` - The dismiss button +- `main` - The area of the toast, snack, or modal which will display the `type` color +- `border` - The border of the component + +These components have been clearly illustrated below. For the time being, the `border` style for `bs5Utils.Snack` cannot +be overridden. + +**Note:** All of these keys _must_ be passed in the `styles` parameter object. + +**Method Overview** + +```javascript +/** + * Register a style for the components + * @param key - To reference your style + * @param styles - The style object + */ +Bs5Utils.registerStyle(key, styles) +``` + +**Usage** + +You first define your CSS classes: + +```css +.bg-pink { + background-color: pink; +} + +.text-purple { + color: purple; +} + +.border-pink { + border-color: pink !important; +} +``` + +Then you register the style: + +```javascript +Bs5Utils.registerStyle('pink', { + btnClose: ['btn-close-white'], + main: ['bg-pink', 'text-purple'], + border: ['border-pink'] +}); +``` + +Pass empty arrays if you wish to leave the default styles e.g. + +```javascript +Bs5Utils.registerStyle('pink', { + btnClose: [], + main: ['bg-pink', 'text-purple'], + border: ['border-pink'] +}); +``` + +Now, `pink` can be used as a `type` when displaying snacks, toasts, or modals e.g. + +**Snack** + +![Theming Snack](example/img/theming-snack.png) + +**Toast** + +![Theming Snack](example/img/theming-toast.png) + +**Modal** + +![Theming Modal](example/img/theming-modal.png) + +API +- + +This package is based around the `Bs5Utils` class, so first things first, construct the object: + +```javascript +const bs5Utils = new Bs5Utils(); +``` + +Thereafter you'll be able to use the methods outlined below. + +### Snacks + +**Method Overview** + +```javascript +/** + * Display a lightweight toast for simple alerts + * @param - type the theme of the snack + * @param - title the title of the of the snack + * @param - delay in ms, if specified the snack will autohide after the specified amount + * @param - dismissible set whether the dismiss button should show + */ +bs5Utils.Snack.show( + type, + title, + delay = 0, + dismissible = true +); +``` + +**Usage** + +```javascript +bs5Utils.Snack.show('secondary', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('light', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('white', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('dark', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('info', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('primary', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('success', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('warning', 'Hello World!', delay = 0, dismissible = true); +bs5Utils.Snack.show('danger', 'Hello World!', delay = 0, dismissible = true); +``` + +**Example** + +![img.png](example/img/api-snacks.png) + +### Toasts + +**Method Overview** + +```javascript +/** + * Display a toast for alerts + * @param type - the theme of the snack + * @param icon - Set an icon in the top-left corner, you can pass HTML directly + * @param title - the title of the of the toast + * @param subtitle - the subtitle of the toast + * @param content - the content of the toast + * @param buttons - the action buttons of the toast + * @param delay - in ms, if specified the snack will autohide after the specified amount + * @param dismissible - set whether the dismiss button should show + */ +bs5Utils.Toast.show({ + type, + icon = '', + title, + subtitle = '', + content = '', + buttons = [], + delay = 0, + dismissible = true, +}); +``` + +**Usage** + +```javascript +bs5Utils.Toast.show({ + type: 'primary', + icon: ``, + title: 'Notification!', + subtitle: '23 secs ago', + content: 'Hello World!', + buttons: [ + { + text: 'Click Me!', + class: 'btn btn-sm btn-primary', + handler: () => { + alert(`Button #1 has been clicked!`); + } + }, + { + text: 'Click Me Too!', + class: 'btn btn-sm btn-warning', + handler: () => { + alert(`You clicked me too!`); + } + }, + { + type: 'dismiss', + text: 'Hide', + class: 'btn btn-sm btn-secondary' + } + ], + delay: 0, + dismissible: true +}); +``` + +**Example** + +![img.png](example/img/api-toasts.png) + +### Modals + +**Method Overview** + +```javascript +/** + * Display a modal + * @param type - the theme of the snack + * @param title - the title of the modal, if omitted, the modal-header element is removed + * @param content - the content of the modal, if omitted, the modal-body element is removed + * @param buttons - any action buttons, if omitted, the the modal-footer element is removed + * @param centered - set whether the modal is centered + * @param dismissible - set whether the dismiss button should show + * @param backdrop - set the type of backdrop: true, false, static + * @param keyboard - set whether the escape key closes the modal + * @param focus - set whether the modal is autofocussed when initialized + * @param fullscreen - set whether the modal is fullscreen + * @param modalSize - set the size of the modal: sm, lg, xl by default, it's an empty string + */ +bs5Utils.Modal.show({ + type, + title = '', + content = '', + buttons = [], + centered = false, + dismissible = true, + backdrop = dismissible ? true : 'static', + keyboard = dismissible, + focus = true, + fullscreen = false, + size = '' +}) +``` + +**Usage** + +```javascript +bs5Utils.Modal.show({ + type: 'primary', + title: `Hello World!`, + content: `

Hello World!

`, + buttons: [ + { + text: 'Click Me!', + class: 'btn btn-sm btn-primary', + handler: () => { + alert(`Button #1 has been clicked!`); + } + }, + { + text: 'Click Me Too!', + class: 'btn btn-sm btn-warning', + handler: () => { + alert(`You clicked me too!`); + } + }, + { + type: 'dismiss', + text: 'Hide', + class: 'btn btn-sm btn-secondary' + } + ], + centered: true, + dismissible: true, + backdrop: 'static', + keyboard: false, + focus: false +}); +``` + +**Example** + +![img.png](example/img/api-modal-1.png) + +![img.png](example/img/api-modal-2.png) + +![img_1.png](example/img/api-modal-3.png) + +![img.png](example/img/api-modal-4.png) + +Support & Contribute +- + +- Use: [Babel Repl](https://babeljs.io/repl) and [JavaScript Minifier](https://javascript-minifier.com/) to build the + app to transpile and minify your changes +- Submit issues and PRs +- Let's know how you're using this package in your project +- If this package has helped you, and you're feeling particularly generous: + - **ETH/MATIC:** 0x6515654c8e931052ab17a63311411D475D503e59 + - **ADA:** addr1qxaqvghsr8lu3wrmql4fcvg6txj5083s2a9rr5dmrrtjt0yn8t0x4yav3ma2flg3tzcu9767s7senydcumnf6c4krnnspn949q \ No newline at end of file diff --git a/dist/js/Bs5Utils.js b/dist/js/Bs5Utils.js new file mode 100644 index 0000000..8343842 --- /dev/null +++ b/dist/js/Bs5Utils.js @@ -0,0 +1 @@ +"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}var _count=new WeakMap;class Modal{constructor(){_classPrivateFieldInitSpec(this,_count,{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,_count,1+ +_classPrivateFieldGet(this,_count)),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,_count)}`),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 _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 Toast{constructor(){_classPrivateFieldInitSpec(this,_count3,{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,_count3,1+ +_classPrivateFieldGet(this,_count3));const l=Bs5Utils.defaults.styles[t],d=l.btnClose.join(" "),c=l.border,b=document.createElement("div");b.setAttribute("id",`toast-${_classPrivateFieldGet(this,_count3)}`),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,_count3)}-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 _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)}}_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"]}}}); \ No newline at end of file diff --git a/example/img/api-modal-1.png b/example/img/api-modal-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6b299a1c131f75262995a70afd5b7c92dfaf0b30 GIT binary patch literal 5490 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 T+x9{J!rYus94T}B?&?1PR2cgo literal 0 HcmV?d00001 diff --git a/example/img/api-modal-2.png b/example/img/api-modal-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b16e5a9c97d24eb00a144b9ae87160ae9cd21db0 GIT binary patch literal 4177 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 b6`EO`KLJkHIUp6f-Sm$bpEi$Q8`v$p!c3#CR* zrU#!Rd?T#ON$1d&0)uw0NXaruihKr#^HQH#_d6E$G8?s{eftGEysq*pkAJJj%J+f& zJQXvHLn@XdQ~oUy3CfLNVrB2;Zjzj-tHe^!;0e(Zc+!QzhGu9!^?GeQ9nFawRUT8z zeILCMkTYtPf^N_eO_w>D^IFa9^(HUHg~0F1jKk>@ND0ZRbR( zJCt$LD9Pks5g%qL$uh}}l2hE9%CB{?(!FKp_VlAP{>#m^>}MI$vN z?LrtmK1i6WQYJXEO=Y1e^3+5(UKyX&m^?=^&NM_Sx||BdrM z&3$NatEG}xi8Ctzuh3^2Xv{b_#kJ=ceZY?ZzpJ;#maaS}xtS)z>DbLc%6imO5_Fdt z%nM^>X*Yo%1Ab?3z4RBVC!fRUHL9vp4;OCQ0hvQd2ZcXjH2gs2tEGoPciy_-Vh6z= da#i2>`kUA%GaFdp(CY~Z4h-E{_t&_*e*)5z?ymp< literal 0 HcmV?d00001 diff --git a/example/img/api-snacks.png b/example/img/api-snacks.png new file mode 100644 index 0000000000000000000000000000000000000000..1b831d9572ee39c19819b2c933ec8b1225249fdd GIT binary patch literal 15748 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 literal 0 HcmV?d00001 diff --git a/example/img/api-toasts.png b/example/img/api-toasts.png new file mode 100644 index 0000000000000000000000000000000000000000..6e84d9a2811adc2a9fd6f23f3e97d2fcde6a963a GIT binary patch literal 5078 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 literal 0 HcmV?d00001 diff --git a/example/img/theming-snack.png b/example/img/theming-snack.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6603663975fd4964f5504464b03bab6dba8071 GIT binary patch literal 4703 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 F=wICWzuEu* literal 0 HcmV?d00001 diff --git a/example/img/theming-toast.png b/example/img/theming-toast.png new file mode 100644 index 0000000000000000000000000000000000000000..42fd468465301a1f81abb99850ee12b445291903 GIT binary patch literal 8064 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�Y2&{)~ z?2=eZZQ?exd|If)qU|^9u+q~2q_*>mV=%$Jl>ehx*Jd31;WvVZJ)ipb+z*Q!9F zak@^sD}ay9L0pyzw-RPTEro0M{q>adhxv&Ev8v*#ak+R6gv3FuqPLy6Uo3kL?Gszj z2OP7S(7XTf%cJ-zhPIX?1UC_?NgE_XZ*jmI{nN2CnuMTBbI*r%sgDqD6EW~KxSSk7 zEX*-{WJuW&joACD0ac3NHoaRhI&u)aF=KQvqK1vqP&bv1xpEBdcvQ&kB7V3{;)@Ss z+!?q0<{XH}z$$dR(c{LXyETz%wecf3)On>SM3FUWSia~qs#Ke0*xsbstMcH{fLF&4 z6V{`^dqqykal!qdU1?>#Dqn|W$wF$IIysL`vM7+sU-HL%oWm_WRuu*QnK0U{Nma#gjg3w@RNXa6EBoPQyNP~F4n5)4GMOTH5DFUh#91}slD%+W zPj?rEOWa0|%N3{Z5e~(iV%3#ZHPn8hw#Q-({an?jEoVVzBv0_KIWdoULUp_BcMq_g z@Qpd{CZO1cO=HArREEESHWwm;$L&89CNx}0Q24SR54ulr^9Ih81+21#L-|+ynIz%vA$E9?eyNZ;8gl@AMpY%^Gies)l(DkH*l4& z?E)bnL(6TjJzZH7fh5o`FAUbp`Oc;w!vJ>oJ;{7-!Q_*pJ0KN1dn~%RC5VQFMBVOt zNmZ>t<=g4nsLi_5;HI(fKfpADX#Uq|%e<+h5_lv?R+nooT|<2n+nskUJ+NCU$$V zaV8cDI`^{3Q)st5JoO8$^J5fggl(NMI~?Z)!fdK|Al)0dd;_dHk_ zv84qm&I2=0$oEE=*$k9$4kCu*q$UKwAq|&+zyLU@av7aL3+<4D5C{u_{u0;#gS>bP z|J4A)orl=(Ag<%i0}y|j|MpGLS^!}U4l69pEa1Z`I`@3~Cj(;m)v+e6@>u>r(f%pbCBM1DiP(TaS9dhluVr>J7!IL4GbWmr z(3dwN!Vf48$=qqq#^Aw__kJ14DsTfz)w01go-Ae68*$}no|Ques@o%;99}nZ=RZTv zY`oF{t0Y3b`#Ee@NoRkGKCf2g9R;S`b-XW%*L39K=$J%{N|SUBH$8XtZ(lse22Toh z90rSur!J$PLY3Pe==)>_V2BdZU6rPUb6V7A=6Kx!W8%~{0Nc03E1HiE{AIn138{ET zx~kt8%{sU_ek1du|AoJg4}#B)*=)b9S%v~r9NSp_$#j?2W`X;@?`nt(xF#tXyGX7i zst)N({^M`XBH{TiLT6;fobbCZsM1WFZ{>obg5L0?(vijU%1@PPGC--v^>Vb}pIcg7DNo6k!wt|2tj@RMjc zzk?k`*&7F+F>PNx5Av3JE_N?3M{VSCBV?7>(Dvi5n>6ADgefTgviryy1oJBI8jdr3 zO0Tx=${e)i{)lm=?+o%z7{lDXR8HNLgY|9aX1$QSaK9EVKl^vNKH~R9l zcNqi8gDcuYEEmX#=}GT+%=$8vez7_*Bcs^YQSG_Gma@8DYysMuA8)Rq_O;ohMi8}h zC|!p0Kww{Ho;byYlgvAo4@In6L@Vz=JH+wDKZgy zPIMGUh(@~M!NI{_@2Da~pMHC0<|xg-Oi{l?tMe^*Oy18?z%Av#j}P}-FWoqtN39Z>z!amQ{; zsKqPSFO~~Mk8`gvjY8-NlhUSap4Zz4#c&aBN$SJYyYl0&>HiB$EF5Px+-NbqVADD* z95Lzc_SV#mXe`4iHVT}~SGW*fF{8u`*y2seH+^cd5HrcuGiDdRP+)7Z zE>hO!33nU>S(b}rCX7;}Jh{aR-frCNdu8kqSC0v6D4G{R*YsoR_xvgsB2&me71@V6 zI(Ze0$>1Huz-I=nGYZX;^yBo`nLK8bB9Mp{uX>=4!Two zu2oe#l1x>Yk{ik06SB9mY6TnW6kV^26#~pX!!x8DW9RQ%MSkl$n^XS` z7m-PiSQHdgZ)ac3MuP2Bp#P0CFPv+GA*X$h-dvkOp@wFewtnfi z;(=_nGO`=iTjw*5)%mDm*jtC0g0;}dO@+cUPEw!iDTKLyfGfwPthzG}_Vivi@K8n* z`@k(y#Kkwl9+Eu0wu0tP^_+7{!zbS%wvc^b#GX(J=P&${Y$>X=8BPTiwnjhh2H(1T zD?HHJYz^(1&?$Hn1!s#$YVl2a`nv7cumXlAOqjoV6`clb5HQ7ebiP>(J^dwA6l=&s zpr>)O*yrW|zJ>k_!`~}dDfEB-XWmgO+}@^bH(26&r{-9v_X?U{P~6lztIX#SW+>bf zm9roGtgd6SGA7H6L~_jb3wI9US65ei0dTLQdVr(%#w7$hc6uynG2<9BH-fo`K9M~i zLk%cmRiyYBdntegZxwt~C1-8y;9J9JOr&B4a!3RCw(EWAb{%}KD*5$O5($5{hW3HB zfX-XFWwAUfeW@`M+9OaJ-W0asG*QQqV2IcCo*ixbH}@;C*33Y&MeR_r%GN#pl|!>!4OhzA z9fbMa9Ko0Bab-Rv?5#U32H%mcRTRTb9lZGIRSyf3+cWd7pQTEMx+zf-X|k>BWY9N` zIwGRg2?5`xOnrv#9wG*voMS(5b;45Z_nn_Uj1>okVrG@O;^6jGq&j8%xA}2XtcG1y z?q>mRP-tM}tIAzqbYVja*Cd96-2nN*VN zd0jJB}wz7Qc?<` z!}yPR!^ABvQuP3vfF%;cYZkW(T}kPuLOmC5e%iFsysK9I|J0NGG9!KCQM|wqTx!m= ze&Q=UY3e~-_9nF&}?vS!`DXuf$M+;C*WQUh(}lnOXJV_r`` z(R*@x?agTXi^`Gh-z;CbNg(DO8@_|UT%;eE&rtYKUCBBN1yVEJrJYcXv6dX9q}h4l zm@}12GfByfEmUj)d-#t`7rT#e;a1=Piu3qDYs)*$4#M%c`y=Rzyx$+~r(q`^@|agD zMfLq`*ed#dSdX*8)PcTb+?P%;q@Ov+wvYEm9ejT?Ft_cd+@u{8HA%4wwW_$geZuDy z*P_T4S{v$KKh}w#h00#cki=oKH`a^3$*Dc<4~jj5sDW|uN0d@b(OssJ_8z@34Ndl~ zwaVtA%BHJl3Pvv|&5Z2&DY5Cbkf3#0`@cq! zR{xVs2Yt?>80`m0VxQoDvgqc?tKT4Op7slTdmi10{%6p*)Co(xGH)At+N5)<0rnR5 zF9e&0T~yHfQq7z=^DlQeSw)k%eN33LrUI`MNZ|YbLl_)fDVDs~G>dktUmwP#hk98L zD@;~46hw20u7zGU_HNSPD?&a(6}b_iLuUVoAP}bjPTbN!2Iq+|(E`Rq>rX{9dgA)h z@Z^IvJ;!veL=q#IiPA9Q0QR-ncWd*TKKwhX5qE=+8NN;>D2Uzwty?LXewc)KNfgT5 z%MQqi^Xq~#n?`o#m3 z@YqN=43^?1jhfTp6=Ep}+SLE&!`vwN<75dlLV8h06Abd<;T%+thg7y5V1w7*@Bk}C z{v>wTuKe$(A$*DHIe*|@yGPx%cuY1#%jj@R$D+{(MMt{Cmj)zEAD+%^`f4+BX@?w} z%{USVcpbx>81J8+Qw ztSfMnyLXpVHe%a4Vz`NSUK9C$kw6Sdxvvd!#JB{@o!-ONT<~vvSf@$j)Oy{m=3v<930odT?voc%Fs~g-l5z+DclKd! zFRL>9n$Jli)Nf{-({ic?g`7g=$Wfj9%xi6>;)cU&DLnu-jXmUaD;r=aQU?O&hfBZl zEBZN|XxJ?V5fPEO@usDky`E1A2_KekL8xiHWlP1vC2yb-qWH#+8(A|okjBi5(y>~ee!LM@BSq^U-3i{~c)W~oUT zlZ2mP@j22I6E)L3dhLz*p`m}3sI4~NJxZq{gD8+vYq;1P{Ivn2cmcfBMtv1M%=J4& zzXQIoRIu2ybUwf}sPxU&v#Xs73XB&ZL9tpuh-+&2gUTjDOLH}^MmtMG)q-by8{OoW zau_eux~)QlK7QOo5OR!`HAKDN4Rm3Hn;zgZZI0x*)c9K*uD6r<&t@jWtA;5|4U44A3VpMrN)}Y+7#Yk+Oox5XblXQKyriaRW)++pu zc@-3Xvz*BHn$_+4!eULLEcH;sM)PxWh?XypLw8d!6de&^;UOJX^eI}FdXWuTmOwU( zC;j6-q#n}69xeN+nz6~7RAU!~P1WMt%X)Yr{&_lxGXgDOTp={{uDk!sqe&WU)Ron+ z$!cV08MZpmRT9!brxjoI)aTZgz28ek5dEQ3pt{SzNCrU$(Rv>A8-`+>v=m{nl4w7r zpY!1i%>NE~@$73BTnejr8>O!os|>#Ac)1JvtCEt6)r_1vY1tSL z2t+R^LyX%z@_S_j1d>Os + + + + + + + + + + + Hello, world! + + + +
+

bs5-utils

+
+ +
+
+
+
+
Bs5Utils.Snack.show
+
+
bs5Utils.Snack.show(
+    type,
+    title,
+    delay = 0,
+    dismissible = true
+);
+
+ +
+ +
+
Bs5Utils.Toast.show
+
+
bs5Utils.Toast.show({
+     type,
+     icon = '',
+     title,
+     subtitle = '',
+     content = '',
+     buttons = [],
+     delay = 0,
+     dismissible = true
+});
+
+ +
+ +
+
Bs5Utils.Modal.show
+
+
bs5Utils.Modal.show({
+     type,
+     title = '',
+     content = '',
+     buttons = [],
+     centered = false,
+     dismissible = true,
+     backdrop = dismissible ? true : 'static',
+     keyboard = dismissible,
+     focus = true,
+     fullscreen = false,
+     size = ''
+});
+
+ +
+
+
+
+ + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fe1b3da --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "bs5-utils", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bootstrap": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", + "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..60afbaa --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "bs5-utils", + "version": "1.0.0", + "description": "A JavaScript utility package for Bootstrap 5 components.", + "main": "src/js/Bs5Utils.js", + "repository": { + "type": "git", + "url": "git+https://github.com/Script47/Toast.git" + }, + "keywords": [ + "bootstrap-5", + "bootstrap" + ], + "author": "Script47 ", + "license": "MIT", + "bugs": { + "url": "https://github.com/Script47/bs5-utils/issues" + }, + "homepage": "https://github.com/Script47/bs5-utils", + "scripts": { + "build": "webpack --config webpack.config.js" + }, + "dependencies": { + "bootstrap": ">=5.0" + }, + "devDependencies": {} +} diff --git a/src/js/Bs5Utils.js b/src/js/Bs5Utils.js new file mode 100644 index 0000000..f65f814 --- /dev/null +++ b/src/js/Bs5Utils.js @@ -0,0 +1,108 @@ +class Bs5Utils { + /** + * Default config options + * @type {{toasts: {container: string, position: string, stacking: boolean}}} + */ + static defaults = { + toasts: { + position: 'top-right', + container: 'toast-container', + stacking: true + }, + + 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'] + } + } + } + + constructor() { + this.#createToastContainer(); + + this.Toast = new Toast(); + this.Snack = new Snack(); + this.Modal = new Modal(); + } + + #createToastContainer() { + let containerEl = document.querySelector(`#${Bs5Utils.defaults.toasts.container}`); + + if (!containerEl) { + const positionToClass = { + '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' + }; + + containerEl = document.createElement('div'); + containerEl.classList.add('position-relative'); + containerEl.setAttribute('aria-live', 'polite'); + containerEl.setAttribute('aria-atomic', 'true'); + containerEl.innerHTML = `
`; + + document.body.appendChild(containerEl); + } + } + + /** + * Register a style for the components + * @param key - To reference your style + * @param styles - The style object + */ + static registerStyle(key, styles) { + if (typeof styles !== 'object' && Array.isArray(styles)) { + throw 'The styles parameter must be an object when you register component style.' + } + + Bs5Utils.defaults.styles[key] = styles; + } +} \ No newline at end of file diff --git a/src/js/components/Modal.js b/src/js/components/Modal.js new file mode 100644 index 0000000..fedf714 --- /dev/null +++ b/src/js/components/Modal.js @@ -0,0 +1,114 @@ +class Modal { + /** + * A counter for the Modals + * @type {number} + */ + #count = 0; + + /** + * Display a modal + * @param type - the theme of the snack + * @param title - the title of the modal, if omitted, the modal-header element is removed + * @param content - the content of the modal, if omitted, the modal-body element is removed + * @param buttons - any action buttons, if omitted, the the modal-footer element is removed + * @param centered - set whether the modal is centered + * @param dismissible - set whether the dismiss button should show + * @param backdrop - set the type of backdrop: true, false, static + * @param keyboard - set whether the escape key closes the modal + * @param focus - set whether the modal is autofocussed when initialized + * @param fullscreen - set whether the modal is fullscreen + * @param modalSize - set the size of the modal: sm, lg, xl by default, it's an empty string + */ + show({ + type, + title = '', + content = '', + buttons = [], + centered = false, + dismissible = true, + backdrop = dismissible ? true : 'static', + keyboard = dismissible, + focus = true, + fullscreen = false, + size = '' + }) { + this.#count++; + + size = ['sm', 'lg', 'xl'].includes(size) ? `modal-${size}` : ''; + fullscreen = fullscreen ? 'modal-fullscreen' : ''; + centered = centered ? 'modal-dialog-centered modal-dialog-scrollable' : ''; + + const style = Bs5Utils.defaults.styles[type], + btnCloseStyles = style.btnClose.join(' '), + borderStyles = style.border, + modal = document.createElement('div'); + + modal.setAttribute('id', `modal-${this.#count}`) + modal.setAttribute('tabindex', '-1'); + modal.classList.add('modal'); + + let footerHtml = '', + buttonIds = []; + + if (Array.isArray(buttons) && buttons.length) { + footerHtml += ``; + } + + modal.innerHTML = ` `; + + document.body.appendChild(modal); + + modal.addEventListener('hidden.bs.modal', function (e) { + e.target.remove(); + }); + + buttonIds.forEach(value => { + document.getElementById(value.id).addEventListener('click', value.handler) + }); + + const opts = { + backdrop, + keyboard, + focus + }; + + const bsModal = new bootstrap.Modal(modal, opts); + + bsModal.show(); + + return bsModal; + } +} \ No newline at end of file diff --git a/src/js/components/Snack.js b/src/js/components/Snack.js new file mode 100644 index 0000000..0c76f78 --- /dev/null +++ b/src/js/components/Snack.js @@ -0,0 +1,61 @@ +class Snack { + /** + * A counter for the Snacks + * @type {number} + */ + #count = 0; + + /** + * Display a lightweight toast + * @param type - the theme of the snack + * @param title - the title of the of the snack + * @param delay - in ms, if specified the snack will autohide after the specified amount + * @param dismissible - set whether the dismiss button should show + */ + show(type, title, delay = 0, dismissible = true) { + this.#count++; + + const style = Bs5Utils.defaults.styles[type], + btnCloseStyle = style.btnClose.join(' '), + snack = document.createElement('div'); + + snack.classList.add('toast', 'align-items-center', 'border-1', 'border-dark'); + style.main.forEach(value => { + snack.classList.add(value); + }); + snack.setAttribute('id', `snack-${this.#count}`); + snack.setAttribute('role', 'alert'); + snack.setAttribute('aria-live', 'assertive'); + snack.setAttribute('aria-atomic', 'true'); + snack.innerHTML = `
+
${title}
+ ${dismissible ? `` : ''} +
`; + + if (!Bs5Utils.defaults.toasts.stacking) { + document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach((toast) => { + toast.remove(); + }); + } + + document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(snack); + + snack.addEventListener('hidden.bs.toast', function (e) { + e.target.remove(); + }); + + const opts = { + autohide: (delay > 0 && typeof delay === 'number'), + }; + + if (delay > 0 && typeof delay === 'number') { + opts['delay'] = delay; + } + + const bsSnack = new bootstrap.Toast(snack, opts); + + bsSnack.show(); + + return bsSnack; + } +} \ No newline at end of file diff --git a/src/js/components/Toast.js b/src/js/components/Toast.js new file mode 100644 index 0000000..490d346 --- /dev/null +++ b/src/js/components/Toast.js @@ -0,0 +1,118 @@ +class Toast { + /** + * A counter for the Toasts + * @type {number} + */ + #count = 0; + + /** + * Display a toast for alerts + * @param type - the theme of the snack + * @param icon - Set an icon in the top-left corner, you can pass HTML directly + * @param title - the title of the of the toast + * @param subtitle - the subtitle of the toast + * @param content - the content of the toast + * @param buttons - the action buttons of the toast + * @param delay - in ms, if specified the snack will autohide after the specified amount + * @param dismissible - set whether the dismiss button should show + */ + show({ + type, + icon = '', + title, + subtitle = '', + content = '', + buttons = [], + delay = 0, + dismissible = true + }) { + this.#count++; + + const style = Bs5Utils.defaults.styles[type], + btnCloseStyles = style.btnClose.join(' '), + borderStyles = style.border, + toast = document.createElement('div'); + + toast.setAttribute('id', `toast-${this.#count}`); + toast.setAttribute('role', 'alert'); + toast.setAttribute('aria-live', 'assertive'); + toast.setAttribute('aria-atomic', 'true'); + + toast.classList.add('toast', 'align-items-center'); + borderStyles.forEach(value => { + toast.classList.add(value); + }); + + let buttonsHtml = ``, + buttonIds = []; + + if (Array.isArray(buttons) && buttons.length) { + buttonsHtml += `
`; + + buttons.forEach((button, key) => { + const type = button.type || 'button'; + + switch (type) { + case 'dismiss': + buttonsHtml += ` `; + break; + + default: + let id = `toast-${this.#count}-button-${key}`; + + buttonsHtml += ` `; + + if (button.hasOwnProperty('handler') && typeof button.handler === 'function') { + buttonIds.push({ + id, + handler: button.handler + }); + } + } + }); + + buttonsHtml += `
`; + } + + toast.innerHTML = `
+ ${icon} + ${title} + ${subtitle} + ${dismissible ? `` : ''} +
+
+ ${content} + ${buttonsHtml} +
`; + + if (!Bs5Utils.defaults.toasts.stacking) { + document.querySelectorAll(`#${Bs5Utils.defaults.toasts.container} .toast`).forEach((toast) => { + toast.remove(); + }); + } + + document.querySelector(`#${Bs5Utils.defaults.toasts.container}`).appendChild(toast); + + toast.addEventListener('hidden.bs.toast', function (e) { + e.target.remove(); + }); + + buttonIds.forEach(value => { + document.getElementById(value.id).addEventListener('click', value.handler) + }); + + const opts = { + autohide: (delay > 0 && typeof delay === 'number'), + }; + + if (delay > 0 && typeof delay === 'number') { + opts['delay'] = delay; + } + + const bsToast = new bootstrap.Toast(toast, opts); + + bsToast.show(); + + return bsToast; + } +} \ No newline at end of file