mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-11 17:55:51 +00:00
New menu
This commit is contained in:
parent
ddc8d42886
commit
2d28420c8d
153
qortal-ui-core/src/functional-components/side-menu-item-style.js
Normal file
153
qortal-ui-core/src/functional-components/side-menu-item-style.js
Normal file
@ -0,0 +1,153 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const sideMenuItemStyle = css`
|
||||
:host {
|
||||
--font-family: "Roboto", sans-serif;
|
||||
--item-font-size: 1rem;
|
||||
--sub-item-font-size: 0.85rem;
|
||||
--item-padding: 1rem;
|
||||
--item-content-padding: 1rem;
|
||||
--icon-height: 1.25rem;
|
||||
--icon-width: 1.25rem;
|
||||
--item-border-radius: 5px;
|
||||
--item-selected-color: #dddddd;
|
||||
--item-selected-color-text: #333333;
|
||||
--item-color-active: #d1d1d1;
|
||||
--item-color-hover: #eeeeee;
|
||||
--item-text-color: #080808;
|
||||
--item-icon-color: #080808;
|
||||
--item-border-color: #eeeeee;
|
||||
--item-border-selected-color: #333333;
|
||||
|
||||
--overlay-box-shadow: 0 2px 4px -1px hsla(214, 53%, 23%, 0.16), 0 3px 12px -1px hsla(214, 50%, 22%, 0.26);
|
||||
--overlay-background-color: #ffffff;
|
||||
|
||||
--spacing: 4px;
|
||||
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
border-radius: var(--item-border-radius);
|
||||
}
|
||||
|
||||
#itemLink {
|
||||
align-items: center;
|
||||
font-size: var(--item-font-size);
|
||||
font-weight: 400;
|
||||
height: var(--icon-height);
|
||||
transition: background-color 200ms;
|
||||
padding: var(--item-padding);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--item-border-color);
|
||||
}
|
||||
|
||||
#itemLink:hover {
|
||||
background-color: var(--item-color-hover);
|
||||
}
|
||||
|
||||
#itemLink:active {
|
||||
background-color: var(--item-color-active);
|
||||
}
|
||||
|
||||
#content {
|
||||
padding-left: var(--item-content-padding);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:host([compact]) #content {
|
||||
padding-left: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([selected]) #itemLink {
|
||||
background-color: var(--item-selected-color);
|
||||
color: var(--item-selected-color-text);
|
||||
border-left: 3px solid var(--item-border-selected-color);
|
||||
}
|
||||
|
||||
:host([selected]) slot[name="icon"]::slotted(*) {
|
||||
color: var(--item-selected-color-text);
|
||||
}
|
||||
|
||||
:host(:not([selected])) #itemLink{
|
||||
color: var(--item-text-color);
|
||||
}
|
||||
|
||||
:host([expanded]){
|
||||
background-color: var(--item-selected-color);
|
||||
}
|
||||
|
||||
:host([hasSelectedChild]){
|
||||
background-color: var(--item-selected-color);
|
||||
}
|
||||
|
||||
:host span {
|
||||
cursor: inherit;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
slot[name="icon"]::slotted(*) {
|
||||
flex-shrink: 0;
|
||||
color: var(--item-icon-color);
|
||||
height: var(--icon-height);
|
||||
width: var(--icon-width);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#collapse-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
:host([compact]) #itemLink[level]:not([level="0"]) {
|
||||
padding: calc( var(--item-padding) / 2);
|
||||
}
|
||||
|
||||
:host(:not([compact])) #itemLink[level]:not([level="0"]) {
|
||||
padding-left: calc(var(--icon-width) + var(--item-content-padding));
|
||||
}
|
||||
|
||||
#itemLink[level]:not([level="0"]) #content {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
width: auto;
|
||||
font-weight: 400;
|
||||
font-size: var(--sub-item-font-size)
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: block;
|
||||
left: 101%;
|
||||
min-width: 200px;
|
||||
padding: 4px 2px;
|
||||
background-color: var(--overlay-background-color);
|
||||
background-image: var(--overlay-background-image, none);
|
||||
box-shadow: var(--overlay-box-shadow);
|
||||
border: 1px solid var(--overlay-background-color);
|
||||
border-left: 0;
|
||||
border-radius: 0 3px 3px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
animation: pop 200ms forwards;
|
||||
}
|
||||
|
||||
@keyframes pop{
|
||||
0% {
|
||||
transform: translateX(-5px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
210
qortal-ui-core/src/functional-components/side-menu-item.js
Normal file
210
qortal-ui-core/src/functional-components/side-menu-item.js
Normal file
@ -0,0 +1,210 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { ifDefined } from 'lit/directives/if-defined.js'
|
||||
import { sideMenuItemStyle } from './side-menu-item-style.js'
|
||||
import '@vaadin/icon'
|
||||
import '@vaadin/icons'
|
||||
import '@polymer/paper-tooltip'
|
||||
|
||||
export class SideMenuItem extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selected: { type: Boolean, reflect: true },
|
||||
label: { type: String, reflect: true },
|
||||
expanded: { type: Boolean, reflect: true },
|
||||
compact: { type: Boolean, reflect: true },
|
||||
href: { type: String, reflect: true },
|
||||
target: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
${sideMenuItemStyle}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selected = false
|
||||
this.expanded = false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._itemLinkTemplate()} ${this._tooltipTemplate()}
|
||||
${this._childrenTemplate()}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated(changedProperties) {
|
||||
if (!this.hasChildren()) {
|
||||
return
|
||||
}
|
||||
this.collapseExpandIcon = document.createElement("vaadin-icon")
|
||||
this.collapseExpandIcon.id = "collapse-button"
|
||||
this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon)
|
||||
this._boundOutsideClickListener = this._outsideClickListener.bind(this)
|
||||
}
|
||||
|
||||
_itemLinkTemplate() {
|
||||
return html`
|
||||
<a id="itemLink"
|
||||
level=${this._getLevel}
|
||||
href=${this.href || '#!'}
|
||||
@click="${(e) => this._onClick(e)}"
|
||||
target=${ifDefined(this.target)}
|
||||
>
|
||||
<slot class="icon" name="icon"></slot>
|
||||
<div id ="content">
|
||||
<span>${this.label}</span>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
}
|
||||
|
||||
_tooltipTemplate() {
|
||||
return html`
|
||||
${this._getLevel === 0 && this.compact ? html`
|
||||
<paper-tooltip for="itemLink" position="right" animation-delay="0">
|
||||
${this.label}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: undefined}
|
||||
`
|
||||
}
|
||||
|
||||
_childrenTemplate() {
|
||||
return html`
|
||||
${this.expanded ? html`
|
||||
${this.compact ? html`
|
||||
<div id="overlay"><slot></slot></div>
|
||||
`
|
||||
: html`
|
||||
<slot></slot>
|
||||
`}
|
||||
`
|
||||
: undefined}
|
||||
`
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
changedProperties.forEach((oldValue, propName) => {
|
||||
if (propName === "compact") {
|
||||
this._onCompactChanged()
|
||||
}
|
||||
|
||||
if (propName === "expanded") {
|
||||
this._onExpandedChanged()
|
||||
}
|
||||
|
||||
if (propName === "selected"){
|
||||
if (oldValue === this.selected){
|
||||
return
|
||||
}
|
||||
|
||||
if (this.selected) {
|
||||
this._changeSelectedState(true)
|
||||
this._markParentWithSelectedChild()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onCompactChanged() {
|
||||
this.expanded = false;
|
||||
|
||||
if (this.collapseExpandIcon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.compact) {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
|
||||
} else {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
|
||||
}
|
||||
}
|
||||
|
||||
_onExpandedChanged() {
|
||||
if (this.collapseExpandIcon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.expanded) {
|
||||
this._onHandleExpanded();
|
||||
} else {
|
||||
this._onHandleCollapsed();
|
||||
}
|
||||
}
|
||||
|
||||
_onHandleExpanded() {
|
||||
if (!this.compact) {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-up-small"
|
||||
} else {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-left-small"
|
||||
document.addEventListener("click", this._boundOutsideClickListener, true)
|
||||
}
|
||||
}
|
||||
|
||||
_onHandleCollapsed() {
|
||||
if (!this.compact) {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
|
||||
} else {
|
||||
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
|
||||
document.removeEventListener(
|
||||
"click",
|
||||
this._boundOutsideClickListener,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_onClick(e) {
|
||||
if (!this.hasChildren()) {
|
||||
this.selected = true
|
||||
} else {
|
||||
this.expanded = !this.expanded
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
_outsideClickListener(event) {
|
||||
const eventPath = event.composedPath()
|
||||
if (eventPath.indexOf(this) < 0) {
|
||||
this.expanded = false
|
||||
}
|
||||
}
|
||||
|
||||
_changeSelectedState(selected, sourceEvent) {
|
||||
this.selected = selected
|
||||
let evt = new CustomEvent("side-menu-item-select", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { sourceEvent: sourceEvent }
|
||||
});
|
||||
this.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
hasChildren() {
|
||||
return !!this.querySelector("side-menu-item")
|
||||
}
|
||||
|
||||
_markParentWithSelectedChild() {
|
||||
let element = this.parentElement;
|
||||
while (element instanceof SideMenuItem) {
|
||||
element.setAttribute('hasSelectedChild', true)
|
||||
element = element.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
get _getLevel() {
|
||||
let level = 0
|
||||
let element = this.parentElement
|
||||
while (element instanceof SideMenuItem) {
|
||||
level++;
|
||||
element = element.parentElement
|
||||
}
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("side-menu-item", SideMenuItem);
|
78
qortal-ui-core/src/functional-components/side-menu.js
Normal file
78
qortal-ui-core/src/functional-components/side-menu.js
Normal file
@ -0,0 +1,78 @@
|
||||
import {LitElement, html, css} from 'lit'
|
||||
|
||||
class SideMenu extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
items: {type: Array},
|
||||
selectedValue: {type: String, reflect: true},
|
||||
compact: {type: Boolean, reflect: true}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
nav {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([compact]) {
|
||||
width: auto;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.compact = false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<nav @side-menu-item-select=${this._handleSelect}>
|
||||
<slot></slot>
|
||||
</nav>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated(_changedProperties) {
|
||||
this.items = [...this.querySelectorAll("side-menu-item")]
|
||||
}
|
||||
|
||||
_handleSelect(event) {
|
||||
let targetItem = event.target
|
||||
this._deselectAllItems()
|
||||
targetItem.selected = true
|
||||
this.selectedValue = targetItem.label
|
||||
}
|
||||
|
||||
_deselectAllItems() {
|
||||
this.items.forEach(element => {
|
||||
if (this.compact) {
|
||||
element.expanded = false
|
||||
}
|
||||
element.selected = false
|
||||
element.hasChildren() ? element.removeAttribute('hasSelectedChild') : undefined
|
||||
});
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
changedProperties.forEach((oldValue, propName) => {
|
||||
if (propName === "compact") {
|
||||
this.items.forEach(item => (item.compact = this.compact))
|
||||
let evt = new CustomEvent("side-menu-compact-change", {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
this.dispatchEvent(evt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("side-menu", SideMenu);
|
Loading…
x
Reference in New Issue
Block a user