mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-12 02:05: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