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"
|
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);
|
||||||
|
|||||||
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>
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user