mirror of
https://github.com/bitinflow/ui.git
synced 2026-03-13 13:45:59 +00:00
Add dropdown (in-dev)
This commit is contained in:
11
src/runtime/components/BitinflowBadge.vue
Normal file
11
src/runtime/components/BitinflowBadge.vue
Normal 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>
|
||||
53
src/runtime/components/BitinflowDropdown.vue
Normal file
53
src/runtime/components/BitinflowDropdown.vue
Normal 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>
|
||||
27
src/runtime/components/BitinflowDropdownItem.vue
Normal file
27
src/runtime/components/BitinflowDropdownItem.vue
Normal 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>
|
||||
@@ -11,14 +11,14 @@
|
||||
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"
|
||||
>
|
||||
<i class="fas fa-ghost mr-2 text-5xl opacity-70" />
|
||||
<i class="fas fa-ghost mr-2 text-5xl opacity-70"/>
|
||||
<div>
|
||||
No resources found
|
||||
</div>
|
||||
<div class="text-xs mt-2 opacity-70">
|
||||
<template v-if="hasCreateListener">
|
||||
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.
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -47,19 +47,21 @@
|
||||
<div class="flex gap-4 py-2">
|
||||
<div>
|
||||
<bitinflow-table-checkbox
|
||||
v-model="checkedAll"
|
||||
@update:model-value="toggleAll"
|
||||
:model-value="selectedItems.length === items.length"
|
||||
@update:model-value="selectAll"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex-auto px-6"
|
||||
class="flex-auto px-6 pr-[7rem]"
|
||||
:class="gridClass"
|
||||
>
|
||||
<template
|
||||
v-for="key in keys"
|
||||
:key="key"
|
||||
>
|
||||
<div class="opacity-80">{{ columns[key].label }}</div>
|
||||
<div class="opacity-80">
|
||||
{{ columns[key].label }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +69,11 @@
|
||||
<bitinflow-table-row
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:options="options"
|
||||
:selected="checkedItems[item.id]"
|
||||
:grid-class="gridClass"
|
||||
@update:selected="select(item)"
|
||||
@click="click(item)"
|
||||
>
|
||||
<slot
|
||||
@@ -78,17 +84,51 @@
|
||||
</bitinflow-table-row>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import BitinflowTableRow from "../../../dist/runtime/components/BitinflowTableRow.vue";
|
||||
import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue";
|
||||
import BitinflowTableFloatingAction from "./BitinflowTableFloatingAction.vue";
|
||||
|
||||
export default {
|
||||
name: "BitinflowTable",
|
||||
components: {BitinflowTableCheckbox, BitinflowTableRow},
|
||||
components: {BitinflowTableFloatingAction, BitinflowTableCheckbox, BitinflowTableRow},
|
||||
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
@@ -106,7 +146,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
keys: Object.keys(this.columns),
|
||||
checkedAll: false,
|
||||
checkedItems: {}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -116,16 +156,36 @@ export default {
|
||||
},
|
||||
hasCreateListener() {
|
||||
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: {
|
||||
title(value) {
|
||||
// "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) {
|
||||
console.log('toggleAll', checked)
|
||||
selectAll(checked) {
|
||||
this.items.forEach(item => {
|
||||
this.checkedItems[item.id] = checked;
|
||||
});
|
||||
},
|
||||
select(item) {
|
||||
this.checkedItems[item.id] = !this.checkedItems[item.id];
|
||||
},
|
||||
click(item) {
|
||||
this.$emit('click', item);
|
||||
|
||||
30
src/runtime/components/BitinflowTableFloatingAction.vue
Normal file
30
src/runtime/components/BitinflowTableFloatingAction.vue
Normal 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>
|
||||
@@ -1,20 +1,50 @@
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<div class="self-center">
|
||||
<bitinflow-table-checkbox v-model="checked" />
|
||||
<bitinflow-table-checkbox
|
||||
:model-value="selected"
|
||||
@update:model-value="select"
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
class="after:absolute after:inset-0"
|
||||
class="after:absolute after:inset-0 z-0"
|
||||
href="#"
|
||||
@click.prevent="click"
|
||||
/>
|
||||
<div
|
||||
class="bg-white border-2 text-black dark:bg-base-700 dark:text-white rounded shadow py-4 px-6"
|
||||
:class="checked ? 'border-primary-500' : 'border-transparent'"
|
||||
:class="gridClass"
|
||||
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>
|
||||
@@ -23,30 +53,47 @@
|
||||
|
||||
<script>
|
||||
import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue";
|
||||
import BitinflowDropdown from "./BitinflowDropdown.vue";
|
||||
import BitinflowDropdownItem from "./BitinflowDropdownItem.vue";
|
||||
|
||||
export default {
|
||||
name: "BitinflowTableRow",
|
||||
components: {BitinflowTableCheckbox},
|
||||
components: {BitinflowDropdownItem, BitinflowDropdown, BitinflowTableCheckbox},
|
||||
|
||||
props: {
|
||||
gridClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
checked: false
|
||||
}
|
||||
},
|
||||
emits: ['click', 'update:selected'],
|
||||
|
||||
methods: {
|
||||
select(value) {
|
||||
this.$emit('update:selected', !value);
|
||||
},
|
||||
click() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user