basic dropdown + dropdown-list

This commit is contained in:
2020-04-07 12:28:47 +02:00
parent a40b04fcaa
commit 1d15b675da
8 changed files with 391 additions and 3 deletions
@@ -0,0 +1,131 @@
<template>
<div class="ui-dropdown-list">
<template v-for="item in items">
<button v-if="!item.type || item.type === 'button'" :disabled="item.disabled" type="button" @click="clicked(item)" class="ui-dropdown-list-item">
<i v-if="item.icon" class="ui-dropdown-list-item-icon" :class="item.icon" :style="{ color: item.color ? item.color : null }"></i>
{{item.name | localize}}
</button>
<hr class="ui-dropdown-list-separator" v-if="item.type === 'separator'">
</template>
</div>
</template>
<script>
export default {
name: 'uiDropdownList',
props: {
items: {
type: Array,
required: true,
default: []
},
action: {
type: Function,
default: () => { }
}
},
data: () => ({
dropdown: null
}),
watch: {
},
mounted ()
{
// find parent dropdown so we can pass it on item click
let current = this;
do
{
if (current.$options.name === 'uiDropdown')
{
this.dropdown = current;
break;
}
}
while (current = current.$parent);
if (!this.dropdown)
{
console.warn('ui-dropdown-list: Could not find parent <ui-dropdown />');
}
},
methods: {
clicked(item)
{
let baseObj = typeof item.action === 'function' ? item : this;
baseObj.action(item, this.dropdown);
}
}
}
</script>
<style lang="scss">
.ui-dropdown-list
{
padding: 5px;
}
.ui-dropdown-list-item
{
display: grid;
width: 100%;
grid-template-columns: 30px 1fr auto;
grid-gap: 6px;
align-items: center;
font-size: var(--font-size);
padding: 0 16px;
height: 46px;
color: var(--color-fg-mid);
border-radius: var(--radius);
&:not([disabled]):hover, &.is-active
{
background: var(--color-highlight);
color: var(--color-fg);
font-weight: 700;
.ui-dropdown-list-item-icon
{
color: var(--color-fg);
}
}
&[disabled]
{
color: var(--color-fg-light);
cursor: default;
pointer-events: none;
.ui-dropdown-list-item-icon
{
color: var(--color-fg-light);
}
}
}
.ui-dropdown-list-item-icon
{
font-size: 18px;
line-height: 1;
font-weight: 400;
position: relative;
top: -1px;
color: var(--color-fg-mid);
transition: color 0.2s ease;
}
.ui-dropdown-list-separator
{
border-bottom-color: var(--color-highlight);
margin: 10px 0;
}
</style>
@@ -0,0 +1,81 @@
<template>
<div class="ui-dropdown-container">
<div class="ui-dropdown-toggle" @click.stop.prevent="toggle">
<slot name="button"></slot>
</div>
<div class="ui-dropdown" role="dialog" v-if="open" v-click-outside="hide">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'uiDropdown',
props: {
},
data: () => ({
open: false
}),
watch: {
},
mounted ()
{
},
methods: {
toggle()
{
if (this.open)
{
this.hide();
}
else
{
this.show();
}
},
show()
{
this.open = true;
},
hide()
{
this.open = false;
}
}
}
</script>
<style lang="scss">
.ui-dropdown-container
{
position: relative;
}
.ui-dropdown
{
position: absolute;
min-width: 300px;
min-height: 200px;
background: var(--color-bg-mid);
border-radius: var(--radius);
border: 1px solid var(--color-line);
box-shadow: 0 0 20px var(--color-shadow);
z-index: 8;
top: calc(100% + 5px);
}
</style>
+5
View File
@@ -66,6 +66,11 @@
display: flex;
align-items: center;
height: 100%;
> * + *
{
margin-left: 15px;
}
}
.ui-header-bar-title
+99
View File
@@ -0,0 +1,99 @@
import Vue from 'vue';
/// calls the passed function when a click happens outside the target element
Vue.directive('click-outside', {
bind(el, binding, vNode)
{
if (!Helpers.validate(binding)) return;
// Define Handler and cache it on the element
function handler(e)
{
if (!vNode.context) return;
// some components may have related popup item, on which we shall prevent the click outside event handler.
var elements = e.path || (e.composedPath && e.composedPath());
elements && elements.length > 0 && elements.unshift(e.target);
if (el.contains(e.target) || Helpers.isPopup(vNode.context.popupItem, elements))
{
return;
}
el.__vueClickOutside__.callback(e);
}
// add Event Listeners
el.__vueClickOutside__ = {
handler: handler,
callback: binding.value
};
!Helpers.isServer(vNode) && document.addEventListener('click', handler);
},
update(el, binding)
{
if (Helpers.validate(binding))
{
el.__vueClickOutside__.callback = binding.value;
}
},
unbind(el, binding, vNode)
{
!Helpers.isServer(vNode) && document.removeEventListener('click', el.__vueClickOutside__.handler);
delete el.__vueClickOutside__;
}
});
var Helpers = {
isServer(vNode)
{
return typeof vNode.componentInstance !== 'undefined' && vNode.componentInstance.$isServer;
},
isPopup(popupItem, elements)
{
if (!popupItem || !elements)
{
return false;
}
for (var i = 0, len = elements.length; i < len; i++)
{
try
{
if (popupItem.contains(elements[i]))
{
return true;
}
if (elements[i].contains(popupItem))
{
return false;
}
}
catch (e)
{
return false;
}
}
return false;
},
validate(binding)
{
if (typeof binding.value !== 'function')
{
console.warn('v-click-outside: provided expression ' + binding.expression + ' is not a function.');
return false;
}
return true;
}
};
+17
View File
@@ -0,0 +1,17 @@
import Vue from 'vue';
/// <summary>
/// This directive is used to bind an element (button or link) to a dropdown
/// </summary>
Vue.directive('dropdown', {
bind(el, binding)
{
console.info(el, binding);
},
update(el, binding)
{
}
});
+3 -1
View File
@@ -1,3 +1,5 @@
import './localize';
import './resizable';
import './resizable';
import './dropdown';
import './clickoutside';
+49 -2
View File
@@ -9,7 +9,12 @@
</div>
<div>
<ui-header-bar title="Checkout" :on-back="onBack">
<ui-button type="light" label="Actions" caret="down" />
<ui-dropdown>
<template v-slot:button>
<ui-button type="light" label="Actions" caret="down" />
</template>
<ui-dropdown-list :items="actions" :action="actionSelected" />
</ui-dropdown>
<ui-button type="light" label="Preview" icon="fth-eye" />
<ui-button label="Save" />
</ui-header-bar>
@@ -32,9 +37,45 @@
max: 520,
save: 'page-tree',
handle: '.ui-resizable'
}
},
actions: []
}),
created()
{
this.actions.push({
name: 'Create',
icon: 'fth-plus'
});
this.actions.push({
name: 'Move',
icon: 'fth-corner-down-right'
});
this.actions.push({
name: 'Copy',
icon: 'fth-copy',
disabled: true
});
this.actions.push({
name: 'Sort',
icon: 'fth-shuffle'
});
this.actions.push({
type: 'separator'
});
this.actions.push({
name: 'Delete',
icon: 'fth-x',
action(item, dropdown)
{
console.info('actionDeleteSelected', item);
dropdown.hide();
}
});
},
methods: {
getItems(parent)
@@ -53,6 +94,12 @@
});
},
actionSelected(item, dropdown)
{
console.info('actionSelected', item);
dropdown.hide();
},
onBack()
{
console.info('back');
+6
View File
@@ -24,11 +24,17 @@
--color-line: #2f3233;
--color-line-mid: #d7d8d9;
// misc colors
--color-shadow: rgba(0,0,0,0.3);
--color-highlight: rgba(255,255,255, 0.05);
// sizes
--padding: 32px;
--padding-s: 24px;
--height-top: 74px;
--radius: 5px;
--font-size: 14px;
--font-size-s: 12px;