From e87177e98a47256c66e0efb554a2a480339b1be9 Mon Sep 17 00:00:00 2001 From: jlao Date: Mon, 1 Apr 2024 17:01:50 +0800 Subject: [PATCH 1/2] control sidebar --- src/scss/_control-sidebar.scss | 170 +++++++++++++++++++ src/scss/_variables.scss | 39 +++++ src/scss/parts/_core.scss | 1 + src/ts/adminlte.ts | 2 + src/ts/control-sidebar.ts | 288 +++++++++++++++++++++++++++++++++ 5 files changed, 500 insertions(+) create mode 100644 src/scss/_control-sidebar.scss create mode 100644 src/ts/control-sidebar.ts diff --git a/src/scss/_control-sidebar.scss b/src/scss/_control-sidebar.scss new file mode 100644 index 00000000000..b5897388830 --- /dev/null +++ b/src/scss/_control-sidebar.scss @@ -0,0 +1,170 @@ +// +// Component: Control Sidebar +// + +html.control-sidebar-animate { + overflow-x: hidden; +} + +.control-sidebar { + position: absolute; + top: $lte-app-header-height; + bottom: $lte-app-footer-height; + z-index: $lte-zindex-control-sidebar; + + &, + &::before { + right: -$lte-control-sidebar-width; + bottom: $lte-app-footer-height; + display: none; + width: $lte-control-sidebar-width; + @include transition(right $lte-transition-speed $lte-transition-fn, display $lte-transition-speed $lte-transition-fn); + } + + &::before { + position: fixed; + top: 0; + z-index: -1; + display: block; + content: ""; + } +} + +body.text-sm { + .control-sidebar { + top: $lte-app-header-height-compact; + bottom: $lte-app-footer-height-compact; + } +} + +.main-header.text-sm ~ .control-sidebar { + top: $lte-app-header-height-compact; +} + +.main-footer.text-sm ~ .control-sidebar { + bottom: $lte-app-footer-height-compact; +} + +.control-sidebar-push-slide { + .content-wrapper, + .main-footer { + @include transition(margin-right $lte-transition-speed $lte-transition-fn); + } +} + +// Control sidebar open state +.control-sidebar-open { + .control-sidebar { + display: block !important; + + &, + &::before { + right: 0; + } + } + + &.control-sidebar-push, + &.control-sidebar-push-slide { + .content-wrapper, + .main-footer { + margin-right: $lte-control-sidebar-width; + } + } +} + +// Control sidebar slide over content state +.control-sidebar-slide-open { + .control-sidebar { + display: block; + + &, + &::before { + right: 0; + @include transition(right $lte-transition-speed $lte-transition-fn, display $lte-transition-speed $lte-transition-fn); + } + } + + &.control-sidebar-push, + &.control-sidebar-push-slide { + .content-wrapper, + .main-footer { + margin-right: $lte-control-sidebar-width; + } + } +} + +// Dark skin +.control-sidebar-dark { + background-color: $lte-sidebar-dark-bg; + + &, + a, + .nav-link { + color: $lte-sidebar-dark-color; + } + + a:hover { + color: $lte-sidebar-dark-hover-color; + } + + // Headers and labels + h1, + h2, + h3, + h4, + h5, + h6, + label { + color: $lte-sidebar-dark-hover-color; + } + + // Tabs + .nav-tabs { + margin-bottom: 5px; + background-color: $lte-sidebar-dark-hover-bg; + border-bottom: 0; + + .nav-item { + margin: 0; + } + + .nav-link { + position: relative; + padding: 10px 20px; + text-align: center; + + &, + &:hover, + &:active, + &:focus, + &.active { + border: 0; + } + + &:hover, + &:active, + &:focus, + &.active { + color: $lte-sidebar-dark-hover-color; + border-top-color: transparent; + border-bottom-color: transparent; + border-left-color: transparent; + } + + &.active { + background-color: $lte-sidebar-dark-bg; + } + } + } + + .tab-pane { + padding: 10px 15px; + } +} + +// Light skin +.control-sidebar-light { + // Background + background-color: $lte-sidebar-light-bg; + border-left: $lte-app-header-bottom-border; +} diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index eb92b20a55d..82ddfbb9250 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -41,6 +41,35 @@ $lte-sidebar-submenu-active-color: $gray-900 !default; $lte-sidebar-submenu-active-bg: rgba($black, .1) !default; $lte-sidebar-header-color: shade-color($gray-800, 5%) !default; + +// Dark sidebar +$lte-sidebar-dark-bg: $dark !default; +$lte-sidebar-dark-hover-bg: rgba(255, 255, 255, .1) !default; +$lte-sidebar-dark-color: #c2c7d0 !default; +$lte-sidebar-dark-hover-color: $white !default; +$lte-sidebar-dark-active-color: $white !default; +$lte-sidebar-dark-submenu-bg: transparent !default; +$lte-sidebar-dark-submenu-color: #c2c7d0 !default; +$lte-sidebar-dark-submenu-hover-color: $white !default; +$lte-sidebar-dark-submenu-hover-bg: $lte-sidebar-dark-hover-bg !default; +$lte-sidebar-dark-submenu-active-color: $lte-sidebar-dark-bg !default; +$lte-sidebar-dark-submenu-active-bg: rgba(255, 255, 255, .9) !default; + + +// Light sidebar +$lte-sidebar-light-bg: $white !default; +$lte-sidebar-light-hover-bg: rgba($black, .1) !default; +$lte-sidebar-light-color: $gray-800 !default; +$lte-sidebar-light-hover-color: $gray-900 !default; +$lte-sidebar-light-active-color: $black !default; +$lte-sidebar-light-submenu-bg: transparent !default; +$lte-sidebar-light-submenu-color: #777 !default; +$lte-sidebar-light-submenu-hover-color: $black !default; +$lte-sidebar-light-submenu-hover-bg: $lte-sidebar-light-hover-bg !default; +$lte-sidebar-light-submenu-active-color: $lte-sidebar-light-hover-color !default; +$lte-sidebar-light-submenu-active-bg: $lte-sidebar-light-submenu-hover-bg !default; + + // SIDEBAR MINI // -------------------------------------------------------- $nav-link-padding-x-compact: .25rem !default; @@ -80,6 +109,11 @@ $lte-app-footer-border-top-color: var(--#{$prefix}border-color) !default; $lte-app-footer-border-top: $lte-app-footer-border-top-width solid $lte-app-footer-border-top-color !default; $lte-app-footer-bg: var(--#{$prefix}body-bg) !default; $lte-app-footer-color: var(--#{$prefix}secondary-color) !default; +$lte-app-footer-height-inner: (1rem + ($lte-app-footer-padding * 2)) !default; +$lte-app-footer-height: (#{$lte-app-footer-height-inner} + #{$lte-app-footer-border-top-width}) !default; +$lte-app-footer-height-compact-inner: (.875rem + ($lte-app-footer-padding-compact * 2)) !default; +$lte-app-footer-height-compact: (#{$lte-app-footer-height-compact-inner} + #{$lte-app-footer-border-top-width}) !default; + // BRAND LINK // -------------------------------------------------------- @@ -116,3 +150,8 @@ $lte-direct-chat-default-msg-border-color: var(--#{$prefix}border-color) !defaul $lte-zindex-app-header: $zindex-fixed + 4 !default; $lte-zindex-sidebar: $zindex-fixed + 8 !default; $lte-zindex-sidebar-overlay: $lte-zindex-sidebar - 1 !default; +$lte-zindex-control-sidebar: $zindex-fixed + 1 !default; + +// CONTROL SIDEBAR +// -------------------------------------------------------- +$lte-control-sidebar-width: $lte-sidebar-width !default; diff --git a/src/scss/parts/_core.scss b/src/scss/parts/_core.scss index 255dc03d794..8779ac201a4 100644 --- a/src/scss/parts/_core.scss +++ b/src/scss/parts/_core.scss @@ -9,6 +9,7 @@ @import "../app-sidebar"; @import "../app-main"; @import "../app-footer"; +@import "../control-sidebar"; @import "../dropdown"; @import "../callouts"; @import "../compact-mode"; diff --git a/src/ts/adminlte.ts b/src/ts/adminlte.ts index 35c24ab200c..9fd814f2946 100644 --- a/src/ts/adminlte.ts +++ b/src/ts/adminlte.ts @@ -4,6 +4,7 @@ import Treeview from './treeview' import DirectChat from './direct-chat' import CardWidget from './card-widget' import FullScreen from './fullscreen' +import ControlSidebar from './control-sidebar' export { Layout, @@ -11,5 +12,6 @@ export { Treeview, DirectChat, CardWidget, + ControlSidebar, FullScreen } diff --git a/src/ts/control-sidebar.ts b/src/ts/control-sidebar.ts new file mode 100644 index 00000000000..b0370f45772 --- /dev/null +++ b/src/ts/control-sidebar.ts @@ -0,0 +1,288 @@ +/** + * -------------------------------------------- + * @file AdminLTE control-sidebar.ts + * @description Control Sidebar for AdminLTE. + * @license MIT + * -------------------------------------------- + */ + +import { + onDOMContentLoaded +} from './util/index' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'ControlSidebar' +const EVENT_COLLAPSED = `collapsed${NAME}` +const EVENT_COLLAPSED_DONE = `collapsed-done${NAME}` +const EVENT_EXPANDED = `expanded${NAME}` + +const SELECTOR_CONTROL_SIDEBAR = '.control-sidebar' +const SELECTOR_CONTROL_SIDEBAR_CONTENT = '.control-sidebar-content' +const SELECTOR_DATA_TOGGLE = '[data-widget="control-sidebar"]' +const SELECTOR_HEADER = '.app-header' +const SELECTOR_FOOTER = '.app-footer' + +const CLASS_NAME_CONTROL_SIDEBAR_ANIMATE = 'control-sidebar-animate' +const CLASS_NAME_CONTROL_SIDEBAR_OPEN = 'control-sidebar-open' +const CLASS_NAME_CONTROL_SIDEBAR_SLIDE = 'control-sidebar-slide-open' +const CLASS_NAME_LAYOUT_FIXED = 'layout-fixed' +const CLASS_NAME_NAVBAR_FIXED = 'layout-navbar-fixed' +const CLASS_NAME_NAVBAR_SM_FIXED = 'layout-sm-navbar-fixed' +const CLASS_NAME_NAVBAR_MD_FIXED = 'layout-md-navbar-fixed' +const CLASS_NAME_NAVBAR_LG_FIXED = 'layout-lg-navbar-fixed' +const CLASS_NAME_NAVBAR_XL_FIXED = 'layout-xl-navbar-fixed' +const CLASS_NAME_FOOTER_FIXED = 'layout-footer-fixed' +const CLASS_NAME_FOOTER_SM_FIXED = 'layout-sm-footer-fixed' +const CLASS_NAME_FOOTER_MD_FIXED = 'layout-md-footer-fixed' +const CLASS_NAME_FOOTER_LG_FIXED = 'layout-lg-footer-fixed' +const CLASS_NAME_FOOTER_XL_FIXED = 'layout-xl-footer-fixed' + +type ControlSidebarOptions = { + controlSidebarSlide: boolean; + scrollbarTheme: string; + scrollbarAutoHide: string; + target: string; + animationSpeed: number; +} + +const Default: ControlSidebarOptions = { + controlSidebarSlide: true, + scrollbarTheme: 'os-theme-light', + scrollbarAutoHide: 'l', + target: SELECTOR_CONTROL_SIDEBAR, + animationSpeed: 300 +} + +// ControlSidebar Class +class ControlSidebar { + private readonly _element: Element + private readonly _config: ControlSidebarOptions + + constructor(element: Element, config: Partial) { + this._element = element + this._config = { ...Default, ...config } + } + + // Public methods + collapse(): void { + const { body, documentElement } = document + const html = documentElement + + // Show the control sidebar + if (this._config.controlSidebarSlide) { + html.classList.add(CLASS_NAME_CONTROL_SIDEBAR_ANIMATE) + body.classList.remove(CLASS_NAME_CONTROL_SIDEBAR_SLIDE) + setTimeout(() => { + const controlSidebar = document?.querySelector(this._config.target) as HTMLElement | undefined + if (controlSidebar) { + controlSidebar.style.display = 'none' + } + + html.classList.remove(CLASS_NAME_CONTROL_SIDEBAR_ANIMATE) + }, 300) + } else { + body.classList.remove(CLASS_NAME_CONTROL_SIDEBAR_OPEN) + } + + this._element.dispatchEvent(new CustomEvent(EVENT_COLLAPSED)) + + setTimeout(() => { + this._element.dispatchEvent(new CustomEvent(EVENT_COLLAPSED_DONE)) + }, this._config.animationSpeed) + } + + show(toggle = false): void { + const { body, documentElement } = document + const html = documentElement + const controlSidebar: HTMLElement = document.querySelector(this._config.target)! + if (toggle) { + controlSidebar.style.display = 'none' + } + + // Collapse the control sidebar + if (this._config.controlSidebarSlide) { + html.classList.add(CLASS_NAME_CONTROL_SIDEBAR_ANIMATE) + setTimeout(() => { + controlSidebar.style.display = 'block' + setTimeout(() => { + body.classList.add(CLASS_NAME_CONTROL_SIDEBAR_SLIDE) + setTimeout(() => { + html.classList.remove(CLASS_NAME_CONTROL_SIDEBAR_ANIMATE) + }, 300) + }, 10) + }) + } else { + body.classList.add(CLASS_NAME_CONTROL_SIDEBAR_OPEN) + } + + this._fixHeight() + this._fixScrollHeight() + + this._element.dispatchEvent(new CustomEvent(EVENT_EXPANDED)) + } + + toggle(): void { + const { body } = document + const target = document.querySelector(this._config.target)! + + const notVisible = !target || getComputedStyle(target).display === 'none' + const shouldClose = body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_OPEN) || body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_SLIDE) + const shouldToggle = notVisible && (body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_OPEN) || body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_SLIDE)) + + if (notVisible || shouldToggle) { + // Open the control sidebar + this.show(notVisible) + } else if (shouldClose) { + // Close the control sidebar + this.collapse() + } + } + + _init(): void { + const { body } = document + const shouldNotHideAll = body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_OPEN) || body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_SLIDE) + + if (shouldNotHideAll) { + const controlSidebar: HTMLElement = document.querySelector(this._config.target)! + if (controlSidebar) { + controlSidebar.style.display = 'block' + } + } else { + const controlSidebars = document.querySelectorAll(SELECTOR_CONTROL_SIDEBAR) + controlSidebars.forEach(sidebar => { + (sidebar as HTMLElement).style.display = 'none' + }) + } + + this._fixHeight() + this._fixScrollHeight() + + window.addEventListener('resize', () => { + this._fixHeight() + this._fixScrollHeight() + }) + + window.addEventListener('scroll', () => { + const shouldFixHeight = body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_OPEN) || body.classList.contains(CLASS_NAME_CONTROL_SIDEBAR_SLIDE) + + if (shouldFixHeight) { + this._fixScrollHeight() + } + }) + } + + private _isNavbarFixed(): boolean { + const { body } = document + return body.classList.contains(CLASS_NAME_NAVBAR_FIXED) || body.classList.contains(CLASS_NAME_NAVBAR_SM_FIXED) || body.classList.contains(CLASS_NAME_NAVBAR_MD_FIXED) || body.classList.contains(CLASS_NAME_NAVBAR_LG_FIXED) || body.classList.contains(CLASS_NAME_NAVBAR_XL_FIXED) + } + + private _isFooterFixed(): boolean { + const { body } = document + return body.classList.contains(CLASS_NAME_FOOTER_FIXED) || body.classList.contains(CLASS_NAME_FOOTER_SM_FIXED) || body.classList.contains(CLASS_NAME_FOOTER_MD_FIXED) || body.classList.contains(CLASS_NAME_FOOTER_LG_FIXED) || body.classList.contains(CLASS_NAME_FOOTER_XL_FIXED) + } + + private _fixScrollHeight(): void { + const { body } = document + const controlSidebar: HTMLElement = document.querySelector(this._config.target)! + + if (!controlSidebar || !body.classList.contains(CLASS_NAME_LAYOUT_FIXED)) { + return + } + + const heights = { + scroll: body.scrollHeight, + window: window.innerHeight, + header: document.querySelector(SELECTOR_HEADER)?.clientHeight ?? 0, + footer: document.querySelector(SELECTOR_FOOTER)?.clientHeight ?? 0 + } + + const positions = { + bottom: Math.abs((heights.window + window.scrollY) - heights.scroll), + top: window.scrollY + } + + const navbarFixed = this._isNavbarFixed() + const footerFixed = this._isFooterFixed() + + const controlSidebarContent: HTMLElement = controlSidebar.querySelector(SELECTOR_CONTROL_SIDEBAR_CONTENT)! + + if (positions.top === 0 && positions.bottom === 0) { + controlSidebar.style.bottom = `${heights.footer}px` + controlSidebar.style.top = `${heights.header}px` + controlSidebarContent.style.height = `${heights.window - (heights.header + heights.footer)}px` + } else if (positions.bottom <= heights.footer) { + if (footerFixed) { + controlSidebar.style.bottom = `${heights.footer}px` + } else { + const top = heights.header - positions.top + controlSidebar.style.bottom = `${heights.footer - positions.bottom}px` + controlSidebar.style.top = `${top >= 0 ? top : 0}px` + controlSidebarContent.style.height = `${heights.window - (heights.footer - positions.bottom)}px` + } + } else if (positions.top <= heights.header) { + if (navbarFixed) { + controlSidebar.style.top = `${heights.header}px` + } else { + controlSidebar.style.top = `${heights.header - positions.top}px` + controlSidebarContent.style.height = `${heights.window - (heights.header - positions.top)}px` + } + } else if (navbarFixed) { + controlSidebar.style.top = `${heights.header}px` + } else { + controlSidebar.style.top = '0px' + controlSidebarContent.style.height = `${heights.window}px` + } + + if (footerFixed && navbarFixed) { + controlSidebarContent.style.height = '100%' + controlSidebar.style.height = '' + } else if (footerFixed || navbarFixed) { + controlSidebarContent.style.height = '100%' + controlSidebar.style.height = '' + } + } + + private _fixHeight(): void { + const { body } = document + const controlSidebarContent: HTMLElement = document.querySelector(`${this._config.target} ${SELECTOR_CONTROL_SIDEBAR_CONTENT}`)! + + if (!controlSidebarContent || !body.classList.contains(CLASS_NAME_LAYOUT_FIXED)) { + return + } + + const heights = { + window: window.innerHeight, + header: document.querySelector(SELECTOR_HEADER)?.clientHeight ?? 0, + footer: document.querySelector(SELECTOR_FOOTER)?.clientHeight ?? 0 + } + + let sidebarHeight = heights.window - heights.header + + if (this._isFooterFixed()) { + sidebarHeight = heights.window - heights.header - heights.footer + } + + controlSidebarContent.style.height = `${sidebarHeight}px` + } +} +// Data API implementation + +onDOMContentLoaded(() => { + const controlSidebarElements = document.querySelectorAll(SELECTOR_DATA_TOGGLE) + controlSidebarElements.forEach(element => { + const controlSidebar = new ControlSidebar(element, Default) + element.addEventListener('click', event => { + event.preventDefault() + controlSidebar.toggle() + }) + controlSidebar._init() + }) +}) + +// Export the class +export default ControlSidebar From 5026bb82429eeb7c40cd87b72af721974a78cdcb Mon Sep 17 00:00:00 2001 From: James Lao Date: Sun, 19 May 2024 05:52:37 +0000 Subject: [PATCH 2/2] add control sidebar buttons and panel --- package-lock.json | 14 +- src/html/components/_head.astro | 3 +- src/html/components/_scripts.astro | 2 +- .../dashboard/_controlsidebar.astro | 336 ++++++++++++++++++ src/html/components/dashboard/_topbar.astro | 8 + src/html/pages/index.astro | 2 + src/html/pages/index2.astro | 2 + src/html/pages/index3.astro | 2 + 8 files changed, 360 insertions(+), 9 deletions(-) create mode 100644 src/html/components/dashboard/_controlsidebar.astro diff --git a/package-lock.json b/package-lock.json index 12ef42b0e2f..be8d9c49b2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2553,9 +2553,9 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.5.2.tgz", - "integrity": "sha512-wSAOgaz48GmhILFElMCeQypSZmj6Ru6DttOOtl3KNkdJ17ApQuGNCfzpk4cClasVrnIu45++2DBwG4LNMQAfaA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz", + "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA==", "dev": true }, "node_modules/@sindresorhus/merge-streams": { @@ -11256,12 +11256,12 @@ } }, "node_modules/shiki": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.5.2.tgz", - "integrity": "sha512-fpPbuSaatinmdGijE7VYUD3hxLozR3ZZ+iAx8Iy2X6REmJGyF5hQl94SgmiUNTospq346nXUVZx0035dyGvIVw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz", + "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==", "dev": true, "dependencies": { - "@shikijs/core": "1.5.2" + "@shikijs/core": "1.6.0" } }, "node_modules/side-channel": { diff --git a/src/html/components/_head.astro b/src/html/components/_head.astro index 7e9beedc7bc..aa26ce2d522 100644 --- a/src/html/components/_head.astro +++ b/src/html/components/_head.astro @@ -27,7 +27,8 @@ const cssPath = isRtl ? ".rtl" : ""; /> - + + diff --git a/src/html/components/_scripts.astro b/src/html/components/_scripts.astro index 2881c7b19f7..a6c5b8fa6e6 100644 --- a/src/html/components/_scripts.astro +++ b/src/html/components/_scripts.astro @@ -4,7 +4,7 @@ const adminlteJsUrl = path + "/js/adminlte.js"; --- - + diff --git a/src/html/components/dashboard/_controlsidebar.astro b/src/html/components/dashboard/_controlsidebar.astro new file mode 100644 index 00000000000..1e50d48abd9 --- /dev/null +++ b/src/html/components/dashboard/_controlsidebar.astro @@ -0,0 +1,336 @@ + + + \ No newline at end of file diff --git a/src/html/components/dashboard/_topbar.astro b/src/html/components/dashboard/_topbar.astro index 4c7e9f29898..23162fcb453 100644 --- a/src/html/components/dashboard/_topbar.astro +++ b/src/html/components/dashboard/_topbar.astro @@ -163,6 +163,14 @@ const distPath = path; + + + +