Add dropdown (in-dev)

This commit is contained in:
René Preuß
2023-02-20 23:46:29 +01:00
parent 4dc1d0a704
commit bf14028a02
6 changed files with 257 additions and 29 deletions

View File

@@ -0,0 +1,11 @@
<template>
<span class="rounded-full bg-zinc-100 dark:bg-base-500 p-1 px-3">
<span class="opacity-70"><slot /></span>
</span>
</template>
<script>
export default {
name: "BitinflowBadge"
}
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div class="relative w-full h-full flex">
<div class="absolute">
<bitinflow-button
class="circle z-10 self-center"
color="light"
variant="solid"
icon="fa-ellipsis-vertical mr-[2px] ml-[2px]"
@click="open = !open"
/>
</div>
<div
v-if="open"
class="absolute top-12 right-8 z-10 pb-3"
>
<div class="rounded bg-zinc-100 dark:bg-base-800 p-3">
<div class="grid gap-3 w-60">
<slot name="menu" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "BitinflowDropdown",
data() {
return {
open: false,
close: () => {
// is clicked outside the dropdown
if (!this.$el.contains(event.target)) {
this.open = false;
}
}
}
},
mounted() {
document.addEventListener('click', this.close);
},
beforeUnmount() {
document.removeEventListener('click', this.close);
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,27 @@
<template>
<button
class="rounded-lg text-left rounded shadow p-2 px-4"
:class="{
'bg-red-500 dark:bg-red-500 hover:bg-red-400': destructive,
'bg-zinc-200 dark:bg-base-700 hover:bg-zinc-300 dark:hover:bg-base-600': !destructive
}"
@click="$emit('click')"
>
<slot />
</button>
</template>
<script>
export default {
name: "BitinflowDropdownItem",
props: {
destructive: {
type: Boolean,
default: false
}
},
emits: ['click']
}
</script>

View File

@@ -11,14 +11,14 @@
v-else-if="items.length === 0" v-else-if="items.length === 0"
class="bg-white dark:bg-base-700 rounded-lg px-4 py-16 text-xl text-center text-black dark:text-white" class="bg-white dark:bg-base-700 rounded-lg px-4 py-16 text-xl text-center text-black dark:text-white"
> >
<i class="fas fa-ghost mr-2 text-5xl opacity-70" /> <i class="fas fa-ghost mr-2 text-5xl opacity-70"/>
<div> <div>
No resources found No resources found
</div> </div>
<div class="text-xs mt-2 opacity-70"> <div class="text-xs mt-2 opacity-70">
<template v-if="hasCreateListener"> <template v-if="hasCreateListener">
Click on the Click on the
<span class="font-bold"><i class="far fa-plus" /> Create Resource</span> <span class="font-bold"><i class="far fa-plus"/> Create Resource</span>
button below to create your first resource. button below to create your first resource.
</template> </template>
<template v-else> <template v-else>
@@ -47,19 +47,21 @@
<div class="flex gap-4 py-2"> <div class="flex gap-4 py-2">
<div> <div>
<bitinflow-table-checkbox <bitinflow-table-checkbox
v-model="checkedAll" :model-value="selectedItems.length === items.length"
@update:model-value="toggleAll" @update:model-value="selectAll"
/> />
</div> </div>
<div <div
class="flex-auto px-6" class="flex-auto px-6 pr-[7rem]"
:class="gridClass" :class="gridClass"
> >
<template <template
v-for="key in keys" v-for="key in keys"
:key="key" :key="key"
> >
<div class="opacity-80">{{ columns[key].label }}</div> <div class="opacity-80">
{{ columns[key].label }}
</div>
</template> </template>
</div> </div>
</div> </div>
@@ -67,7 +69,11 @@
<bitinflow-table-row <bitinflow-table-row
v-for="item in items" v-for="item in items"
:key="item.id" :key="item.id"
:item="item"
:options="options"
:selected="checkedItems[item.id]"
:grid-class="gridClass" :grid-class="gridClass"
@update:selected="select(item)"
@click="click(item)" @click="click(item)"
> >
<slot <slot
@@ -78,17 +84,51 @@
</bitinflow-table-row> </bitinflow-table-row>
</div> </div>
</div> </div>
<div
v-if="selectedItems.length && options.length"
class="fixed inset-x-0 bottom-0 pb-2 sm:pb-5 z-[100]"
>
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div class="rounded-lg bg-base-800 p-2 shadow-lg sm:p-3">
<div class="flex justify-between gap-3">
<div class="self-center text-white text-xl ml-4">
{{ selectedItems.length }} items selected
</div>
<div class="flex gap-3">
<template
v-for="option in options"
:key="option.label"
>
<bitinflow-table-floating-action
:icon="option.icon"
:destructive="option.destructive"
@click="option.action(selectedItems)"
>
{{ option.label }}
</bitinflow-table-floating-action>
</template>
</div>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
import BitinflowTableRow from "../../../dist/runtime/components/BitinflowTableRow.vue"; import BitinflowTableRow from "../../../dist/runtime/components/BitinflowTableRow.vue";
import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue"; import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue";
import BitinflowTableFloatingAction from "./BitinflowTableFloatingAction.vue";
export default { export default {
name: "BitinflowTable", name: "BitinflowTable",
components: {BitinflowTableCheckbox, BitinflowTableRow}, components: {BitinflowTableFloatingAction, BitinflowTableCheckbox, BitinflowTableRow},
props: { props: {
options: {
type: Object,
default: () => ({})
},
columns: { columns: {
type: Array, type: Array,
required: true required: true
@@ -106,7 +146,7 @@ export default {
data() { data() {
return { return {
keys: Object.keys(this.columns), keys: Object.keys(this.columns),
checkedAll: false, checkedItems: {}
} }
}, },
@@ -116,16 +156,36 @@ export default {
}, },
hasCreateListener() { hasCreateListener() {
return this.$attrs && this.$attrs.onCreate; return this.$attrs && this.$attrs.onCreate;
},
selectedItems() {
return this.items.filter(item => this.checkedItems[item.id]);
} }
}, },
mounted() {
this.items.forEach(item => {
// throw warning if id is not present
if (!item.id) {
console.warn(`BitinflowTable: item does not have an id`, item);
}
this.checkedItems[item.id] = false;
});
},
methods: { methods: {
title(value) { title(value) {
// "humanized" by converting kebab-case, snake_case, and camelCase to individual words and capitalizes each word. // "humanized" by converting kebab-case, snake_case, and camelCase to individual words and capitalizes each word.
return value.replace(/([A-Z])/g, ' $1').replace(/[-_]/g, ' ').toLowerCase().replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }); return value.replace(/([A-Z])/g, ' $1').replace(/[-_]/g, ' ').toLowerCase().replace(/(?:^|\s)\S/g, function (a) {
return a.toUpperCase();
});
}, },
toggleAll(checked) { selectAll(checked) {
console.log('toggleAll', checked) this.items.forEach(item => {
this.checkedItems[item.id] = checked;
});
},
select(item) {
this.checkedItems[item.id] = !this.checkedItems[item.id];
}, },
click(item) { click(item) {
this.$emit('click', item); this.$emit('click', item);

View File

@@ -0,0 +1,30 @@
<template>
<button
class="bg-base-700 hover:bg-base-600 rounded-lg text-center p-2 text-white w-32 text-xs grid"
:class="{ 'bg-red-500 hover:bg-red-400': destructive }"
>
<i :class="`fal text-xl ${icon}`" />
<slot />
</button>
</template>
<script>
export default {
name: "BitinflowTableFloatingAction",
props: {
icon: {
type: String,
default: 'fa-play'
},
destructive: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,20 +1,50 @@
<template> <template>
<div class="flex gap-4"> <div class="flex gap-4">
<div class="self-center"> <div class="self-center">
<bitinflow-table-checkbox v-model="checked" /> <bitinflow-table-checkbox
:model-value="selected"
@update:model-value="select"
/>
</div> </div>
<div class="flex-auto relative"> <div class="flex-auto relative">
<div
class="bg-white border-2 text-black dark:bg-base-700 dark:text-white rounded shadow"
:class="selected ? 'border-primary-500' : 'border-transparent'"
>
<div class="flex gap-3">
<div class="flex relative w-full">
<a <a
class="after:absolute after:inset-0" class="after:absolute after:inset-0 z-0"
href="#" href="#"
@click.prevent="click" @click.prevent="click"
/> />
<div <div
class="bg-white border-2 text-black dark:bg-base-700 dark:text-white rounded shadow py-4 px-6" :class="gridClass"
:class="checked ? 'border-primary-500' : 'border-transparent'" class="items-center flex-1 px-6 py-4"
> >
<div :class="gridClass" class="items-center"> <slot/>
<slot /> </div>
</div>
<div class="w-20 h-18 flex justify-center items-center">
<div class="w-1/2 h-11">
<bitinflow-dropdown>
<template #menu>
<template
v-for="option in options"
:key="option.name"
>
<bitinflow-dropdown-item
:destructive="option.destructive"
@click="option.action([item])"
>
<i :class="['fal mr-2', option.icon ? option.icon : 'fa-play']"/>
{{ option.label }}
</bitinflow-dropdown-item>
</template>
</template>
</bitinflow-dropdown>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -23,30 +53,47 @@
<script> <script>
import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue"; import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue";
import BitinflowDropdown from "./BitinflowDropdown.vue";
import BitinflowDropdownItem from "./BitinflowDropdownItem.vue";
export default { export default {
name: "BitinflowTableRow", name: "BitinflowTableRow",
components: {BitinflowTableCheckbox}, components: {BitinflowDropdownItem, BitinflowDropdown, BitinflowTableCheckbox},
props: { props: {
gridClass: { gridClass: {
type: String, type: String,
required: true required: true
},
selected: {
type: Boolean,
required: true
},
options: {
type: Array,
default: () => []
},
item: {
type: Object,
required: true
} }
}, },
emits: ['click'], emits: ['click', 'update:selected'],
data() {
return {
checked: false
}
},
methods: { methods: {
select(value) {
this.$emit('update:selected', !value);
},
click() { click() {
this.$emit('click'); this.$emit('click');
} }
} }
} }
</script> </script>
<style scoped>
.circle {
border-radius: 100%;
}
</style>