diff --git a/src/pages/widgets.html b/src/pages/widgets.html index ce52478a7..079c63234 100644 --- a/src/pages/widgets.html +++ b/src/pages/widgets.html @@ -5,7 +5,7 @@ @@include('./_head.html', { "path": "..", - "title": "AdminLTE 4 | General Form Elements" + "title": "AdminLTE 4 | Widgets" }) @@ -400,6 +400,93 @@ + + +

Cards

+
Abilities
+
+
+
+
+

Expandable

+ +
+ +
+ +
+ +
+ The body of the card +
+ +
+ +
+ +
+
+
+

Collapsable

+ +
+ +
+ +
+ +
+ The body of the card +
+ +
+ +
+ +
+
+
+

Removable

+ +
+ +
+ +
+ +
+ The body of the card +
+ +
+ +
+ +
+
+
+

Maximizable

+ +
+ +
+ +
+ +
+ The body of the card +
+ +
+ +
+ +
+ diff --git a/src/scss/_cards.scss b/src/scss/_cards.scss index 0ccad2d8d..ee37cb91b 100644 --- a/src/scss/_cards.scss +++ b/src/scss/_cards.scss @@ -44,7 +44,7 @@ overflow: auto; } - [data-card-widgett="collapse"] { + [data-card-widget="collapse"] { display: none; } diff --git a/src/ts/adminlte.ts b/src/ts/adminlte.ts index 7243f6fd5..aceb39764 100644 --- a/src/ts/adminlte.ts +++ b/src/ts/adminlte.ts @@ -4,6 +4,7 @@ import SidebarHover from './sidebar-hover' import SidebarOverlay from './sidebar-overlay' import Treeview from './treeview' import DirectChat from './direct-chat' +import CardWidget from './card-widget' export { Layout, @@ -11,7 +12,8 @@ export { SidebarHover, SidebarOverlay, Treeview, - DirectChat + DirectChat, + CardWidget } // diff --git a/src/ts/card-widget.ts b/src/ts/card-widget.ts new file mode 100644 index 000000000..4cb24a2cf --- /dev/null +++ b/src/ts/card-widget.ts @@ -0,0 +1,228 @@ +/** + * -------------------------------------------- + * AdminLTE card-widget.js + * License MIT + * -------------------------------------------- + */ + +import { + domReady, + slideUp, + slideDown +} from './util/index' + +/** + * Constants + * ==================================================== + */ + +const CLASS_NAME_CARD = 'card' +const CLASS_NAME_COLLAPSED = 'collapsed-card' +const CLASS_NAME_COLLAPSING = 'collapsing-card' +const CLASS_NAME_EXPANDING = 'expanding-card' +const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed' +const CLASS_NAME_MAXIMIZED = 'maximized-card' + +const SELECTOR_DATA_REMOVE = '[data-card-widget="remove"]' +const SELECTOR_DATA_COLLAPSE = '[data-card-widget="collapse"]' +const SELECTOR_DATA_MAXIMIZE = '[data-card-widget="maximize"]' +const SELECTOR_CARD = `.${CLASS_NAME_CARD}` +const SELECTOR_CARD_HEADER = '.card-header' +const SELECTOR_CARD_BODY = '.card-body' +const SELECTOR_CARD_FOOTER = '.card-footer' + +const Default = { + animationSpeed: 500, + collapseTrigger: SELECTOR_DATA_COLLAPSE, + removeTrigger: SELECTOR_DATA_REMOVE, + maximizeTrigger: SELECTOR_DATA_MAXIMIZE, + collapseIcon: 'fa-minus', + expandIcon: 'fa-plus', + maximizeIcon: 'fa-expand', + minimizeIcon: 'fa-compress' +} + +interface Settings { + animationSpeed: number; + collapseTrigger: string; + removeTrigger: string; + maximizeTrigger: string; + collapseIcon: string; + expandIcon: string; + maximizeIcon: string; + minimizeIcon: string; +} + +class CardWidget { + _element: HTMLElement + _parent: HTMLElement | null + _settings: Settings + constructor(element: HTMLElement, settings: Settings) { + this._element = element + this._parent = element.closest(SELECTOR_CARD) + + if (element.classList.contains(CLASS_NAME_CARD)) { + this._parent = element + } + + this._settings = Object.assign({}, Default, settings) + } + + collapse() { + this._parent?.classList.add(CLASS_NAME_COLLAPSING) + + const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`) + + if (elm !== undefined) { + for (const el of elm) { + if (el instanceof HTMLElement) { + slideUp(el, this._settings.animationSpeed) + } + } + } + + setTimeout(() => { + this._parent?.classList.add(CLASS_NAME_COLLAPSED) + this._parent?.classList.remove(CLASS_NAME_COLLAPSING) + }, this._settings.animationSpeed) + + const icon = this._parent?.querySelector(`${SELECTOR_CARD_HEADER} ${this._settings.collapseTrigger} .${this._settings.collapseIcon}`) + + icon?.classList.add(this._settings.expandIcon) + icon?.classList.remove(this._settings.collapseIcon) + } + + expand() { + this._parent?.classList.add(CLASS_NAME_EXPANDING) + + const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`) + + if (elm !== undefined) { + for (const el of elm) { + if (el instanceof HTMLElement) { + slideDown(el, this._settings.animationSpeed) + } + } + } + + setTimeout(() => { + this._parent?.classList.remove(CLASS_NAME_COLLAPSED) + this._parent?.classList.remove(CLASS_NAME_EXPANDING) + }, this._settings.animationSpeed) + + const icon = this._parent?.querySelector(`${SELECTOR_CARD_HEADER} ${this._settings.collapseTrigger} .${this._settings.expandIcon}`) + + icon?.classList.add(this._settings.collapseIcon) + icon?.classList.remove(this._settings.expandIcon) + } + + remove() { + if (this._parent) { + slideUp(this._parent, this._settings.animationSpeed) + } + } + + toggle() { + if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) { + this.expand() + return + } + + this.collapse() + } + + maximize() { + if (this._parent) { + const maxElm = this._parent.querySelector(`${this._settings.maximizeTrigger} .${this._settings.maximizeIcon}`) + maxElm?.classList.add(this._settings.minimizeIcon) + maxElm?.classList.remove(this._settings.maximizeIcon) + + this._parent.style.height = `${this._parent.scrollHeight}px` + this._parent.style.width = `${this._parent.scrollWidth}px` + this._parent.style.transition = 'all .15s' + + setTimeout(() => { + document.querySelector('html')?.classList.add(CLASS_NAME_MAXIMIZED) + this._parent?.classList.add(CLASS_NAME_MAXIMIZED) + if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) { + this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED) + } + }, 150) + } + } + + minimize() { + if (this._parent) { + const minElm = this._parent.querySelector(`${this._settings.maximizeTrigger} .${this._settings.minimizeIcon}`) + + minElm?.classList.add(this._settings.maximizeIcon) + minElm?.classList.remove(this._settings.minimizeIcon) + + this._parent.style.cssText = `height: ${this._parent.style.height} !important; width: ${this._parent.style.width} !important; transition: all .15s;` + // console.log('🚀 ~ file: card-widget.ts ~ line 164 ~ CardWidget ~ minimize ~ this._parent.style.height', this._parent.style.height) + + setTimeout(() => { + document.querySelector('html')?.classList.remove(CLASS_NAME_MAXIMIZED) + if (this._parent) { + this._parent.classList.remove(CLASS_NAME_MAXIMIZED) + + if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) { + this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED) + } + } + }, 10) + } + } + + toggleMaximize() { + if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) { + this.minimize() + return + } + + this.maximize() + } +} + +/** + * + * Data Api implementation + * ==================================================== + */ + +domReady(() => { + const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE) + + for (const btn of collapseBtn) { + btn.addEventListener('click', event => { + event.preventDefault() + const target = event.target as HTMLElement + const data = new CardWidget(target, Default) + data.toggle() + }) + } + + const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE) + + for (const btn of removeBtn) { + btn.addEventListener('click', event => { + event.preventDefault() + const target = event.target as HTMLElement + const data = new CardWidget(target, Default) + data.remove() + }) + } + + const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE) + + for (const btn of maxBtn) { + btn.addEventListener('click', event => { + event.preventDefault() + const target = event.target as HTMLElement + const data = new CardWidget(target, Default) + data.toggleMaximize() + }) + } +}) + +export default CardWidget diff --git a/src/ts/util/index.ts b/src/ts/util/index.ts index 37e15963b..fa14c2dfc 100644 --- a/src/ts/util/index.ts +++ b/src/ts/util/index.ts @@ -14,7 +14,85 @@ const windowReady = (callBack: () => void): void => { } } +/* SLIDE UP */ +const slideUp = (target: HTMLElement, duration = 500) => { + target.style.transitionProperty = 'height, margin, padding' + target.style.transitionDuration = `${duration}ms` + target.style.boxSizing = 'border-box' + target.style.height = `${target.offsetHeight}px` + target.style.overflow = 'hidden' + + window.setTimeout(() => { + target.style.height = '0' + target.style.paddingTop = '0' + target.style.paddingBottom = '0' + target.style.marginTop = '0' + target.style.marginBottom = '0' + }, 1) + + window.setTimeout(() => { + target.style.display = 'none' + target.style.removeProperty('height') + target.style.removeProperty('padding-top') + target.style.removeProperty('padding-bottom') + target.style.removeProperty('margin-top') + target.style.removeProperty('margin-bottom') + target.style.removeProperty('overflow') + target.style.removeProperty('transition-duration') + target.style.removeProperty('transition-property') + }, duration) +} + +/* SLIDE DOWN */ +const slideDown = (target: HTMLElement, duration = 500) => { + target.style.removeProperty('display') + let {display} = window.getComputedStyle(target) + if (display === 'none') { + display = 'block' + } + + target.style.display = display + const height = target.offsetHeight + target.style.overflow = 'hidden' + target.style.height = '0' + target.style.paddingTop = '0' + target.style.paddingBottom = '0' + target.style.marginTop = '0' + target.style.marginBottom = '0' + + window.setTimeout(() => { + target.style.boxSizing = 'border-box' + target.style.transitionProperty = 'height, margin, padding' + target.style.transitionDuration = `${duration}ms` + target.style.height = `${height}px` + target.style.removeProperty('padding-top') + target.style.removeProperty('padding-bottom') + target.style.removeProperty('margin-top') + target.style.removeProperty('margin-bottom') + }, 1) + + window.setTimeout(() => { + target.style.removeProperty('height') + target.style.removeProperty('overflow') + target.style.removeProperty('transition-duration') + target.style.removeProperty('transition-property') + }, duration) +} + +/* TOOGLE */ +const slideToggle = (target: HTMLElement, duration = 500) => { + if (window.getComputedStyle(target).display === 'none') { + slideDown(target, duration) + return + } + + slideUp(target, duration) +} + export { domReady, - windowReady + windowReady, + slideUp, + slideDown, + slideToggle }