From 844ec95349de13434379726acc0fa57d8cc553de Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 8 Nov 2022 22:44:09 +0000 Subject: [PATCH 1/2] improve sidebar --- .../src/components/Layout/sidebar/index.jsx | 394 +++++++----------- .../src/components/Layout/sidebar/index.less | 258 ++++++------ packages/app/src/theme/fixments.less | 25 +- packages/app/src/theme/index.less | 31 +- packages/app/src/theme/vars.less | 3 + 5 files changed, 325 insertions(+), 386 deletions(-) diff --git a/packages/app/src/components/Layout/sidebar/index.jsx b/packages/app/src/components/Layout/sidebar/index.jsx index 98af04ef..4d6357bd 100755 --- a/packages/app/src/components/Layout/sidebar/index.jsx +++ b/packages/app/src/components/Layout/sidebar/index.jsx @@ -1,59 +1,99 @@ import React from "react" -import { Layout, Menu, Avatar } from "antd" +import { Menu, Avatar } from "antd" +import { Translation } from "react-i18next" import classnames from "classnames" import config from "config" import { Icons, createIconRender } from "components/Icons" import { sidebarKeys as defaultSidebarItems } from "schemas/defaultSettings" + import sidebarItems from "schemas/routes.json" -import { Translation } from "react-i18next" -import { SidebarEditor } from "./components" import "./index.less" -const { Sider } = Layout - const onClickHandlers = { settings: (event) => { window.app.openSettings() }, } +const getSidebarComponents = () => { + const items = {} + + sidebarItems.forEach((item, index) => { + items[item.id] = { + ...item, + index, + content: ( + <> + {createIconRender(item.icon)} {item.title} + + ), + } + }) + + return items +} + +const generateItems = () => { + const components = getSidebarComponents() + const itemsMap = [] + const pathResolvers = {} + + const keys = window.app?.settings.get("sidebarKeys") ?? defaultSidebarItems + + // filter undefined components to avoid error + keys.filter((key) => { + if (typeof components[key] !== "undefined") { + return true + } + }) + + keys.forEach((key, index) => { + const component = components[key] + + try { + // avoid if item is duplicated + if (itemsMap.includes(component)) { + return false + } + + if (typeof component.path !== "undefined") { + pathResolvers[component.id] = component.path + } + + itemsMap.push(component) + } catch (error) { + return console.log(error) + } + }) + + return { + itemsMap, + pathResolvers, + } +} + export default class Sidebar extends React.Component { constructor(props) { super(props) this.controller = window.app["SidebarController"] = { toggleVisibility: this.toggleVisibility, - toggleEdit: this.toggleEditMode, toggleElevation: this.toggleElevation, - attachElement: this.attachElement, + toggleCollapse: this.toggleCollapse, isVisible: () => this.state.visible, - isEditMode: () => this.state.visible, isCollapsed: () => this.state.collapsed, } this.state = { - editMode: false, visible: false, - loading: true, - collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false, - pathResolve: {}, - menus: {}, - extraItems: { - bottom: [], - top: [], - }, elevated: false, - additionalElements: [], + collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false, + pathResolvers: null, + menus: null, } - window.app.eventBus.on("edit_sidebar", () => this.toggleEditMode()) - - window.app.eventBus.on("settingChanged.sidebar_collapse", (value) => { - this.toggleCollapse(value) - }) - // handle sidedrawer open/close window.app.eventBus.on("sidedrawer.hasDrawers", () => { this.toggleElevation(true) @@ -66,112 +106,21 @@ export default class Sidebar extends React.Component { collapseDebounce = null componentDidMount = async () => { - await this.loadSidebarItems() + await this.loadItems() setTimeout(() => { this.controller.toggleVisibility(true) }, 100) } - getStoragedKeys = () => { - return window.app.settings.get("sidebarKeys") - } - - attachElement = (element) => { - this.setState({ - additionalElements: [...this.state.additionalElements, element], - }) - } - - appendItem = (item = {}) => { - const { position } = item - - if (typeof position === "undefined" && typeof this.state.extraItems[position] === "undefined") { - console.error("Invalid position") - return false - } - - const state = this.state.extraItems - - state[position].push(item) - - this.setState({ extraItems: state }) - } - - loadSidebarItems = () => { - const items = {} - const itemsMap = [] - - // parse all items from schema - sidebarItems.forEach((item, index) => { - items[item.id] = { - ...item, - index, - content: ( - <> - {createIconRender(item.icon)} {item.title} - - ), - } - }) - - // filter undefined to avoid error - let keys = (this.getStoragedKeys() ?? defaultSidebarItems).filter((key) => { - if (typeof items[key] !== "undefined") { - return true - } - }) - - // short items - keys.forEach((id, index) => { - const item = items[id] - - if (item.locked) { - if (item.index !== index) { - keys = keys.move(index, item.index) - - //update index - window.app.settings.set("sidebarKeys", keys) - } - } - }) - - // set items from scoped keys - keys.forEach((key, index) => { - const item = items[key] - - try { - // avoid if item is duplicated - if (itemsMap.includes(item)) { - return false - } - - let valid = true - - if (typeof item.requireState === "object") { - const { key, value } = item.requireState - //* TODO: check global state - } - - // end validation - if (!valid) { - return false - } - - if (typeof item.path !== "undefined") { - let resolvers = this.state.pathResolve ?? {} - resolvers[item.id] = item.path - this.setState({ pathResolve: resolvers }) - } - - itemsMap.push(item) - } catch (error) { - return console.log(error) - } - }) + loadItems = async () => { + const generation = generateItems() // update states - this.setState({ items, menus: itemsMap, loading: false }) + await this.setState({ + menus: generation.itemsMap, + pathResolvers: generation.pathResolvers, + }) } renderMenuItems(items) { @@ -184,29 +133,25 @@ export default class Sidebar extends React.Component { return items.map((item) => { if (Array.isArray(item.children)) { - return ( - - - {t => t(item.title)} - - } - {...item.props} - > - {this.renderMenuItems(item.children)} - - ) + return + + {t => t(item.title)} + + } + {...item.props} + > + {this.renderMenuItems(item.children)} + } - return ( - - - {t => t(item.title ?? item.id)} - - - ) + return + + {t => t(item.title ?? item.id)} + + }) } @@ -223,27 +168,14 @@ export default class Sidebar extends React.Component { if (typeof onClickHandlers[e.key] === "function") { return onClickHandlers[e.key](e) } - if (typeof this.state.pathResolve[e.key] !== "undefined") { - return window.app.setLocation(`/${this.state.pathResolve[e.key]}`, 150) - } - return window.app.setLocation(`/${e.key}`, 150) - } - - toggleEditMode = (to) => { - if (typeof to === "undefined") { - to = !this.state.editMode - } - - if (to) { - window.app.eventBus.emit("clearAllOverlays") - } else { - if (this.itemsMap !== this.getStoragedKeys()) { - this.loadSidebarItems() + if (typeof this.state.pathResolvers === "object") { + if (typeof this.state.pathResolvers[e.key] !== "undefined") { + return window.app.setLocation(`/${this.state.pathResolvers[e.key]}`, 150) } } - this.setState({ editMode: to, collapsed: false }) + return window.app.setLocation(`/${e.key}`, 150) } toggleCollapse = (to) => { @@ -261,11 +193,12 @@ export default class Sidebar extends React.Component { } onMouseEnter = () => { - if (window.app.settings.is("collapseOnLooseFocus", false)) { - return false - } + if (!this.state.visible) return + + if (window.app.settings.is("collapseOnLooseFocus", false)) return clearTimeout(this.collapseDebounce) + this.collapseDebounce = null if (this.state.collapsed) { @@ -274,49 +207,29 @@ export default class Sidebar extends React.Component { } handleMouseLeave = () => { - if (window.app.settings.is("collapseOnLooseFocus", false)) { - return false - } + if (!this.state.visible) return + + if (window.app.settings.is("collapseOnLooseFocus", false)) return if (!this.state.collapsed) { this.collapseDebounce = setTimeout(() => { this.toggleCollapse(true) }, window.app.settings.get("autoCollapseDelay") ?? 500) } } - renderExtraItems = (position) => { - return this.state.extraItems[position].map((item = {}) => { - if (typeof item.icon !== "undefined") { - if (typeof item.props !== "object") { - item.props = Object() - } - - item.props["icon"] = createIconRender(item.icon) - } - - return {item.children} - }) - } - render() { - if (this.state.loading) return null - - const { user } = this.props + if (!this.state.menus) return null return ( - this.props.onCollapse()} className={ classnames( - "sidebar", + "app_sidebar", { - ["edit_mode"]: this.state.editMode, + ["collapsed"]: this.state.visible && this.state.collapsed, + ["elevated"]: this.state.visible && this.state.elevated, ["hidden"]: !this.state.visible, - ["elevated"]: this.state.elevated } ) } @@ -327,70 +240,49 @@ export default class Sidebar extends React.Component { - {this.state.editMode && ( -
- -
- )} +
+ + {this.renderMenuItems(this.state.menus)} + +
- {!this.state.editMode && ( -
- - {this.renderMenuItems(this.state.menus)} - {this.renderExtraItems("top")} - -
- )} - - {!this.state.editMode &&
- {this.state.additionalElements} -
} - - {!this.state.editMode && ( -
- - } overrideEvent="app.openSearcher" > +
+ + } overrideEvent="app.openSearcher" > + + {(t) => t("Search")} + + + } overrideEvent="app.openCreator" > + + {(t) => t("Create")} + + + } overrideEvent="app.openNotifications"> + + {t => t("Notifications")} + + + }> + + {t => t("Settings")} + + + { + app.userData && + + + } + { + !app.userData && }> - {(t) => t("Search")} + {t => t("Login")} - } overrideEvent="app.openCreator" > - - {(t) => t("Create")} - - - } overrideEvent="app.openNotifications"> - - {t => t("Notifications")} - - - }> - - {t => t("Settings")} - - - - { - user && -
- -
-
- } - - { - !user && }> - - {t => t("Login")} - - - } - - {this.renderExtraItems("bottom")} -
-
- )} - + } +
+
+ ) } } \ No newline at end of file diff --git a/packages/app/src/components/Layout/sidebar/index.less b/packages/app/src/components/Layout/sidebar/index.less index c7f7df63..f03eaa5a 100755 --- a/packages/app/src/components/Layout/sidebar/index.less +++ b/packages/app/src/components/Layout/sidebar/index.less @@ -1,18 +1,45 @@ @import "theme/vars.less"; -// SIDEBAR -.ant-layout-sider { +.app_sidebar { + position: relative; + display: flex; + flex-direction: column; + + left: 0; + top: 0; + z-index: 1000; - - background: var(--sidebar-background-color) !important; - background-color: var(--sidebar-background-color) !important; - - border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0; overflow: hidden; - border: 1px solid var(--sidebar-background-color); + + width: @app_sidebar_collapsed_width; + min-width: @app_sidebar_width; + + height: 100vh; + + padding: 10px 0; transition: all 150ms ease-in-out; + background-color: var(--sidebar-background-color); + + border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0; + border: 1px solid var(--sidebar-background-color); + + &.collapsed { + width: 80px; + min-width: 80px; + + .app_sidebar_menu_wrapper { + .ant-menu { + .ant-menu-item:not(.user_avatar) { + .ant-menu-title-content { + animation: disappear 0.3s ease-out forwards; + } + } + } + } + } + &.hidden { flex: 0 !important; min-width: 0 !important; @@ -23,138 +50,127 @@ &.elevated { box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important; } -} -.ant-menu-item { - color: var(--background-color-contrast); + .app_sidebar_header { + display: flex; + flex-direction: column; - h1, - h2, - h3, - h4, - h5, - h6, - span, - p { - color: var(--background-color-contrast); + height: 17%; + + padding: 10px 0; + + .app_sidebar_header_logo { + display: flex; + align-items: center; + justify-content: center; + + img { + user-select: none; + --webkit-user-select: none; + + width: 80%; + max-height: 80px; + } + + &.collapsed { + img { + max-width: 40px; + } + } + } } -} -.ant-menu, -.ant-menu ul { - background: transparent !important; - background-color: transparent !important; + .app_sidebar_menu_wrapper { + height: 65%; + width: 100%; - border-right: 0 !important; -} + overflow: overlay; + overflow-x: hidden; -.sidebar .ant-layout-sider-children { - margin-top: 15px !important; - margin-bottom: 15px !important; + display: flex; + flex-direction: column; - background: transparent !important; - background-color: transparent !important; + align-items: center; - user-select: none; - --webkit-user-select: none; + transition: all 150ms ease-in-out; - transition: all 150ms ease-in-out; - height: 100%; - display: flex; - flex-direction: column; + &.bottom { + position: absolute; - &.edit_mode .ant-layout-sider-children { - background: transparent !important; - background-color: transparent !important; + bottom: 0; + left: 0; - margin-top: 15px !important; + height: fit-content; - .app_sidebar_menu_wrapper { - opacity: 0; - height: 0; - overflow: hidden; + padding-bottom: 30px; + } + + .ant-menu { + display: flex; + flex-direction: column; + + justify-content: center; + align-items: center; + + width: 100%; + + transition: all 150ms ease-in-out; + + .ant-menu-item { + display: flex; + align-items: center; + justify-content: center; + + width: 90%; + + padding: 0 10px !important; + margin: 5px 0 !important; + + transition: all 150ms ease-in-out; + + .ant-menu-title-content { + flex: 1; + } + + &.user_avatar { + .ant-menu-title-content { + display: inline-flex; + align-items: center; + justify-content: center; + width: fit-content; + } + + margin: 0; + padding: 0; + } + + svg { + width: fit-content; + margin: 0 !important; + + height: 16px; + } + } } } } -.app_sidebar_menu_wrapper { - transition: all 450ms ease-in-out; - - height: 100%; - width: 100%; -} - -.app_sidebar_header { - background: transparent !important; - background-color: transparent !important; - - user-select: none; - --webkit-user-select: none; - - display: flex; - flex-direction: column; - height: 15%; - margin-top: 5%; - margin-bottom: 5%; -} - -.app_sidebar_header_logo { - user-select: none; - --webkit-user-select: none; - - display: flex; - align-items: center; - justify-content: center; - - img { - user-select: none; - --webkit-user-select: none; - - width: 80%; - max-height: 80px; +@keyframes disappear { + 0% { + opacity: 1; + width: 100%; } - &.collapsed { - img { - max-width: 40px; - } + 95% { + opacity: 0; + width: 0px; + margin: 0; } -} -.app_sidebar_menu { - background: transparent !important; - background-color: transparent !important; - - height: 65%; - overflow: overlay; - overflow-x: hidden; -} - -.app_sidebar_bottom { - position: absolute; - bottom: 0; - padding-bottom: 30px; - z-index: 50; - left: 0; - - background: transparent !important; - background-color: transparent !important; - backdrop-filter: blur(10px); - --webkit-backdrop-filter: blur(10px); - - width: 100%; - height: fit-content; - - .ant-menu, - ul { - background: transparent !important; - background-color: transparent !important; + 100% { + opacity: 0; + width: 0px; + margin: 0; + flex: 0; } -} - -.user_avatar { - display: flex; - align-items: center; - justify-content: center; - padding: 0 !important; } \ No newline at end of file diff --git a/packages/app/src/theme/fixments.less b/packages/app/src/theme/fixments.less index 46c64e77..007e4654 100755 --- a/packages/app/src/theme/fixments.less +++ b/packages/app/src/theme/fixments.less @@ -172,6 +172,29 @@ } // fix menu colors +.ant-menu, +.ant-menu ul { + background: transparent !important; + background-color: transparent !important; + + border-right: 0 !important; + + .ant-menu-item { + color: var(--background-color-contrast); + + h1, + h2, + h3, + h4, + h5, + h6, + span, + p { + color: var(--background-color-contrast); + } + } +} + .ant-menu-item { color: var(--text-color); border-radius: 8px; @@ -391,6 +414,6 @@ // fix adm cards .adm-card { background: var(--background-color-accent); - + color: var(--text-color); } \ No newline at end of file diff --git a/packages/app/src/theme/index.less b/packages/app/src/theme/index.less index ad8869b8..e917bed7 100755 --- a/packages/app/src/theme/index.less +++ b/packages/app/src/theme/index.less @@ -119,29 +119,25 @@ html { } } -.ant-layout, -.content_layout { - width: 100%; - height: 100%; - max-height: 100vh; - - background-color: transparent; -} - .app_layout { - background-color: rgba(var(--layoutBackgroundColor), var(--backgroundColorTransparency)) !important; - backdrop-filter: blur(var(--backgroundBlur)); - position: relative; - -webkit-overflow-scrolling: touch; width: 100%; height: 100%; max-height: 100vh; overflow: hidden; + + display: flex; + flex-direction: row; + + background-color: rgba(var(--layoutBackgroundColor), var(--backgroundColorTransparency)) !important; + backdrop-filter: blur(var(--backgroundBlur)); + transition: all 150ms ease-in-out; + -webkit-overflow-scrolling: touch; + ::-webkit-scrollbar { display: block; position: absolute; @@ -164,6 +160,15 @@ html { } } +.ant-layout, +.content_layout { + width: 100%; + height: 100%; + max-height: 100vh; + + background-color: transparent; +} + .layout_page { position: relative; -webkit-overflow-scrolling: touch; diff --git a/packages/app/src/theme/vars.less b/packages/app/src/theme/vars.less index 8532cd78..18bf2437 100755 --- a/packages/app/src/theme/vars.less +++ b/packages/app/src/theme/vars.less @@ -1,5 +1,8 @@ //* Now this only works as an fallback for unset dynamic theme values +@app_sidebar_collapsed_width: 80px; +@app_sidebar_width: 230px; + // borders & radius @app_sidebar_borderRadius: 18px; From e05759dc52147c0e4959f85b199aeb0dafe5cbc5 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 8 Nov 2022 22:45:26 +0000 Subject: [PATCH 2/2] uset ext-overflow: clip --- packages/app/src/components/Layout/sidebar/index.less | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/components/Layout/sidebar/index.less b/packages/app/src/components/Layout/sidebar/index.less index f03eaa5a..05a4518e 100755 --- a/packages/app/src/components/Layout/sidebar/index.less +++ b/packages/app/src/components/Layout/sidebar/index.less @@ -130,6 +130,7 @@ .ant-menu-title-content { flex: 1; + text-overflow: clip; } &.user_avatar {