mirror of
https://github.com/bitinflow/ui.git
synced 2026-04-28 11:56:18 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26b40d4c8c | ||
|
|
87bd79a661 | ||
|
|
378301d758 | ||
|
|
583aeacf37 | ||
|
|
56bd212ab6 | ||
|
|
389af82cc3 | ||
|
|
98c6326e32 | ||
|
|
bf14028a02 | ||
|
|
4dc1d0a704 | ||
|
|
d9041f471e | ||
|
|
40d02e52ec | ||
|
|
81df3fceb6 | ||
|
|
7ed407119b | ||
|
|
567ed71dbe | ||
|
73ba21634e
|
|||
|
6942976175
|
|||
|
|
dda0cf2df5 | ||
|
|
ba9aa2e1b2 | ||
|
|
9dab7e782e | ||
|
69663ba7fa
|
|||
|
|
15fee4c0b4 | ||
|
|
ae31a8b08b | ||
|
|
bd3b24f2de | ||
|
38af9291cd
|
|||
|
|
8ae2b9587c | ||
|
|
e4b4354a65 | ||
|
|
f3711e2c83 | ||
|
|
fb166d4d7e | ||
|
|
7bf7a1273a | ||
|
|
b40848aee2 |
@@ -1,6 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## v0.0.6
|
||||||
|
|
||||||
|
## v0.0.5
|
||||||
|
|
||||||
## v0.0.4
|
## v0.0.4
|
||||||
|
|
||||||
## v0.0.3
|
## v0.0.3
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitinflow/ui",
|
"name": "@bitinflow/ui",
|
||||||
"version": "0.0.4",
|
"version": "0.0.6",
|
||||||
"description": "bitinflow UI Kit",
|
"description": "bitinflow UI Kit",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -30,7 +30,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/kit": "^3.2.0",
|
"@nuxt/kit": "^3.2.0",
|
||||||
"@pinia/nuxt": "^0.4.3",
|
"@pinia/nuxt": "^0.4.3",
|
||||||
"pinia": "^2.0.23"
|
"pinia": "^2.0.23",
|
||||||
|
"vue-boring-avatars": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^0.1.1",
|
"@nuxt/eslint-config": "^0.1.1",
|
||||||
|
|||||||
55
src/runtime/components/BitinflowAlert.vue
Normal file
55
src/runtime/components/BitinflowAlert.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="rounded shadow px-8 py-4"
|
||||||
|
:class="`${color}-${variant}`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="title"
|
||||||
|
class="font-bold"
|
||||||
|
>{{ title }}:</span>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowAlert",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary'
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'solid'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'md'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.primary-solid {
|
||||||
|
@apply bg-primary-500 text-white;
|
||||||
|
}
|
||||||
|
.success-solid {
|
||||||
|
@apply bg-teal-400 text-white;
|
||||||
|
}
|
||||||
|
.info-solid {
|
||||||
|
@apply bg-blue-700 text-white;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
@apply bg-yellow-700 text-white;
|
||||||
|
}
|
||||||
|
.danger {
|
||||||
|
@apply bg-rose-700 text-white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
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>
|
||||||
@@ -1,19 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<button
|
||||||
href="#"
|
class="truncate rounded-lg px-4 py-2 border-2 border-transparent"
|
||||||
class="truncate text-black dark:text-white hover:bg-zinc-100 dark:bg-base-700 dark:hover:bg-base-600 dark:text-white rounded-lg px-4 py-2"
|
:class="computedClass"
|
||||||
|
@click="click"
|
||||||
>
|
>
|
||||||
<slot />
|
<i
|
||||||
</a>
|
v-if="icon"
|
||||||
|
:class="['fal mr-1', icon]"
|
||||||
|
/>
|
||||||
|
<slot v-if="!loading" />
|
||||||
|
<template v-else>
|
||||||
|
<i class="fas fa-spinner fa-spin" />
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowButton",
|
name: "BitinflowButton",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'md'
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'base'
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'solid'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
emits: ['click'],
|
emits: ['click'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedClass: function () {
|
||||||
|
return `button-text-${this.size} ${this.color}-${this.variant} ${this.disabled ? 'disabled' : ''} ${this.loading ? 'loading' : ''}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
click() {
|
click() {
|
||||||
|
if (this.disabled || this.loading) return;
|
||||||
this.$emit('click');
|
this.$emit('click');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,5 +64,83 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-sm {
|
||||||
|
@apply text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-lg {
|
||||||
|
@apply text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-xl {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-2xl {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-3xl {
|
||||||
|
@apply text-3xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-4xl {
|
||||||
|
@apply text-4xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-5xl {
|
||||||
|
@apply text-5xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text-6xl {
|
||||||
|
@apply text-6xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-solid {
|
||||||
|
@apply bg-primary-500 hover:bg-primary-600 dark:bg-primary-500 dark:hover:bg-primary-400 text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-outline {
|
||||||
|
@apply border-primary-500 hover:bg-primary-500 dark:hover:bg-primary-500 dark:border-primary-500 dark:hover:border-primary-500 text-primary-500 hover:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-solid {
|
||||||
|
@apply bg-rose-500 hover:bg-rose-600 dark:bg-rose-500 dark:hover:bg-rose-400 text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-outline {
|
||||||
|
@apply border-rose-500 hover:bg-rose-500 dark:hover:bg-rose-500 dark:border-rose-500 dark:hover:border-rose-500 text-rose-500 hover:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-solid {
|
||||||
|
@apply bg-amber-500 hover:bg-amber-600 dark:bg-amber-500 dark:hover:bg-amber-400 text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-outline {
|
||||||
|
@apply border-amber-500 hover:bg-amber-500 dark:hover:bg-amber-500 dark:border-amber-500 dark:hover:border-amber-500 text-amber-500 hover:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-solid {
|
||||||
|
@apply bg-white hover:bg-zinc-100 dark:bg-base-700 dark:hover:bg-base-600 text-black dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-outline {
|
||||||
|
@apply border-zinc-100 hover:bg-zinc-100 dark:hover:bg-base-700 dark:border-base-600 dark:hover:border-base-600 text-black dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-solid {
|
||||||
|
@apply bg-zinc-200 hover:bg-zinc-300 dark:bg-base-600 dark:hover:bg-base-500 text-black dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-outline {
|
||||||
|
@apply border-zinc-200 hover:bg-zinc-200 hover:border-zinc-200 dark:hover:bg-base-500 dark:border-base-500 dark:hover:border-base-500 text-black dark:text-white;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
33
src/runtime/components/BitinflowButtonLink.vue
Normal file
33
src/runtime/components/BitinflowButtonLink.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<nuxt-link
|
||||||
|
class="truncate text-black dark:text-white hover:bg-zinc-100 dark:bg-base-700 dark:hover:bg-base-600 dark:text-white rounded-lg px-4 py-2"
|
||||||
|
:to="to"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</nuxt-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowButton",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['click'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
this.$emit('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
15
src/runtime/components/BitinflowCardFooter.vue
Normal file
15
src/runtime/components/BitinflowCardFooter.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="py-4 px-8 border-t dark:border-base-800 rounded-b">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowCardFooter"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
58
src/runtime/components/BitinflowDropdown.vue
Normal file
58
src/runtime/components/BitinflowDropdown.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative w-full h-full flex">
|
||||||
|
<div class="absolute">
|
||||||
|
<bitinflow-button
|
||||||
|
class="circle z-10 self-center"
|
||||||
|
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,
|
||||||
|
closeListener: () => {
|
||||||
|
// is clicked outside the dropdown
|
||||||
|
if (!this.$el.contains(event.target)) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener('click', this.closeListener);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
document.removeEventListener('click', this.closeListener);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.open = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
||||||
54
src/runtime/components/BitinflowFirstLevelHref.vue
Normal file
54
src/runtime/components/BitinflowFirstLevelHref.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<a
|
||||||
|
class="hover:bg-primary-500 rounded-lg text-center text-xs py-4 flex flex-col space-y-2"
|
||||||
|
:class="calculateClasses"
|
||||||
|
:href="href"
|
||||||
|
>
|
||||||
|
<i :class="['fal text-xl', icon]" />
|
||||||
|
<span>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowFirstLevelLink",
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'fa-arrow-up-right-from-square'
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: '/'
|
||||||
|
},
|
||||||
|
exact: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
calculateClasses: function () {
|
||||||
|
return {
|
||||||
|
'router-link-active':
|
||||||
|
this.exact
|
||||||
|
? document.location.href === this.href
|
||||||
|
: document.location.href.includes(this.href) && this.href !== '/',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.router-link-active {
|
||||||
|
@apply bg-primary-500 dark:bg-primary-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.router-link-active:hover {
|
||||||
|
@apply bg-primary-600 dark:bg-primary-400;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<a class="hover:bg-primary-500 rounded-lg text-center text-xs py-4 flex flex-col space-y-2" href="#">
|
<nuxt-link
|
||||||
<i :class="['fal text-xl', icon]"></i>
|
class="hover:bg-primary-500 rounded-lg text-center text-xs py-4 flex flex-col space-y-2"
|
||||||
|
:class="calculateClasses"
|
||||||
|
:to="to"
|
||||||
|
>
|
||||||
|
<i :class="['fal text-xl', icon]" />
|
||||||
<span>
|
<span>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -14,7 +18,29 @@ export default {
|
|||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'fa-arrow-up-right-from-square'
|
default: 'fa-arrow-up-right-from-square'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
default: '/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
calculateClasses: function () {
|
||||||
|
return {
|
||||||
|
'router-link-active': this.$route.path.includes(this.to) && this.to !== '/',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.router-link-active {
|
||||||
|
@apply bg-primary-500 dark:bg-primary-500;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.router-link-active:hover {
|
||||||
|
@apply bg-primary-600 dark:bg-primary-400;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3,17 +3,27 @@
|
|||||||
<!-- Overlay -->
|
<!-- Overlay -->
|
||||||
<div
|
<div
|
||||||
ref="overlay"
|
ref="overlay"
|
||||||
class="sm:hidden hidden bg-gray-800 opacity-50 w-full min-h-screen absolute z-10"
|
class="sm:hidden hidden bg-base-800 opacity-50 w-full min-h-screen absolute z-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="bg-white relative shadow sm:hidden z-10">
|
<header class="bg-white text-black shadow dark:bg-base-800 dark:text-white relative shadow sm:hidden z-10">
|
||||||
<div class="container flex justify-between items-center py-4">
|
<div class="container flex justify-between items-center p-4">
|
||||||
|
<button
|
||||||
|
class="bitinflow-logo"
|
||||||
|
@click="toggleDarkMode"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/img/icon-light.svg"
|
src="https://cdn.bitinflow.com/ui/images/brand/icon-light.svg"
|
||||||
class="h-4"
|
class="h-8 hidden dark:block"
|
||||||
alt="bitinflow"
|
alt="bitinflow"
|
||||||
>
|
>
|
||||||
|
<img
|
||||||
|
src="https://cdn.bitinflow.com/ui/images/brand/icon.svg"
|
||||||
|
class="h-8 block dark:hidden"
|
||||||
|
alt="bitinflow"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
@@ -31,10 +41,11 @@
|
|||||||
class="inline-block flex"
|
class="inline-block flex"
|
||||||
to="/"
|
to="/"
|
||||||
>
|
>
|
||||||
<img
|
<Avatar
|
||||||
class="h-8 w-8 rounded-full"
|
:size="32"
|
||||||
alt="profile"
|
variant="beam"
|
||||||
src="/img/avatar.jpg"
|
name="John Doe"
|
||||||
|
:colors="colors"
|
||||||
/>
|
/>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
@@ -70,9 +81,12 @@
|
|||||||
>
|
>
|
||||||
<!-- logo -->
|
<!-- logo -->
|
||||||
<div class="flex-none bg-primary-500 h-16 sm:h-20 py-3 sm:py-6 text-white flex flex-initial justify-center">
|
<div class="flex-none bg-primary-500 h-16 sm:h-20 py-3 sm:py-6 text-white flex flex-initial justify-center">
|
||||||
<button @click="toggleDarkMode">
|
<button
|
||||||
|
class="bitinflow-logo"
|
||||||
|
@click="toggleDarkMode"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/img/icon-light.svg"
|
src="https://cdn.bitinflow.com/ui/images/brand/icon-light.svg"
|
||||||
class="h-8 w-auto"
|
class="h-8 w-auto"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
>
|
>
|
||||||
@@ -101,10 +115,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Avatar from "vue-boring-avatars";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowFirstLevelMenu",
|
name: "BitinflowFirstLevelMenu",
|
||||||
|
components: {
|
||||||
|
Avatar
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
darkMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
count: 0,
|
||||||
|
colors: ["#59FFE8", "#00BFA5", "#00F2D1", "#26FFE1", "#8CFFEF"],
|
||||||
|
//colors: ["#A6FFF3", "#00352E", "#00BFA5", "#59FFE8", "#A6FFF3"],
|
||||||
primaryMenu: [
|
primaryMenu: [
|
||||||
{name: 'Home', icon: 'fal fa-home', to: '/', exact: true},
|
{name: 'Home', icon: 'fal fa-home', to: '/', exact: true},
|
||||||
{name: 'Buckets', icon: 'fal fa-bucket', to: '/buckets', exact: false},
|
{name: 'Buckets', icon: 'fal fa-bucket', to: '/buckets', exact: false},
|
||||||
@@ -117,6 +145,12 @@ export default {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
// restore dark mode from local storage
|
||||||
|
if (localStorage.getItem('darkMode') === 'true') {
|
||||||
|
document.body.classList.add('dark');
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
this.$refs.overlay.classList.toggle('hidden');
|
this.$refs.overlay.classList.toggle('hidden');
|
||||||
@@ -127,7 +161,26 @@ export default {
|
|||||||
this.$refs.sidebar.classList.add('-translate-x-full');
|
this.$refs.sidebar.classList.add('-translate-x-full');
|
||||||
},
|
},
|
||||||
toggleDarkMode() {
|
toggleDarkMode() {
|
||||||
document.body.classList.toggle('dark')
|
if (!this.darkMode) return;
|
||||||
|
this.count++;
|
||||||
|
if (this.count % 4 === 0) {
|
||||||
|
console.log('rotate');
|
||||||
|
const buttons = document.querySelectorAll('.bitinflow-logo');
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
button.classList.add('rotate-360');
|
||||||
|
button.classList.add('transition-transform');
|
||||||
|
button.classList.add('duration-500');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// remove all classes
|
||||||
|
button.classList.remove('rotate-360');
|
||||||
|
button.classList.remove('transition-transform');
|
||||||
|
button.classList.remove('duration-500');
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const result = document.body.classList.toggle('dark')
|
||||||
|
localStorage.setItem('darkMode', result ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -141,4 +194,8 @@ export default {
|
|||||||
.nuxt-link-active:hover {
|
.nuxt-link-active:hover {
|
||||||
background: #004F44;
|
background: #004F44;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rotate-360 {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
15
src/runtime/components/BitinflowFlex1.vue
Normal file
15
src/runtime/components/BitinflowFlex1.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowFlex1"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowFlex"
|
name: "BitinflowFlexAuto"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
17
src/runtime/components/BitinflowHeader.vue
Normal file
17
src/runtime/components/BitinflowHeader.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="text-2xl dark:text-white">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowHeader",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
65
src/runtime/components/BitinflowInput.vue
Normal file
65
src/runtime/components/BitinflowInput.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="opacity-80 mb-1">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="block w-full text-black dark:text-white border-2 border-zinc-200 dark:border-base-600 focus:outline-none dark:bg-base-700 dark:text-white rounded-lg px-4 py-2"
|
||||||
|
:class="calculatedClass"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
@change="change"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowInput",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
calculatedClass() {
|
||||||
|
if (this.size !== 'normal') {
|
||||||
|
return `text-${this.size}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
change(e) {
|
||||||
|
this.$emit('update:modelValue', e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
11
src/runtime/components/BitinflowMuted.vue
Normal file
11
src/runtime/components/BitinflowMuted.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-black dark:text-white opacity-50 text-sm">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowMuted"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
16
src/runtime/components/BitinflowPlaceholder.vue
Normal file
16
src/runtime/components/BitinflowPlaceholder.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="animate-pulse">
|
||||||
|
<div class="grid gap-8">
|
||||||
|
<div class="h-9 w-1/3 bg-white dark:bg-base-700 rounded-full" />
|
||||||
|
<div class="h-32 w-full bg-white dark:bg-base-700 rounded-lg" />
|
||||||
|
<div class="h-9 w-1/3 bg-white dark:bg-base-700 rounded-full" />
|
||||||
|
<div class="h-60 w-full bg-white dark:bg-base-700 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowPlaceholder"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,11 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-y-auto h-screen">
|
<div
|
||||||
|
class="overflow-y-auto h-screen"
|
||||||
|
:class="`${variant}`"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowScreenScrollContainer"
|
name: "BitinflowScreenScrollContainer",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.light::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
.light::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
.dark .light::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-base-800;
|
||||||
|
}
|
||||||
|
.dark .light::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-base-800;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
25
src/runtime/components/BitinflowSecondLevelButton.vue
Normal file
25
src/runtime/components/BitinflowSecondLevelButton.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<bitinflow-button
|
||||||
|
class="flex items-center gap-2 w-full"
|
||||||
|
icon="fa-plus"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</bitinflow-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BitinflowButton from "./BitinflowButton.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BitinflowSecondLevelLink",
|
||||||
|
components: {BitinflowButton},
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'fa-arrow-up-right-from-square'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['click']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,20 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<bitinflow-button class="flex items-center gap-2">
|
<bitinflow-button-link
|
||||||
<i :class="['fal', icon]" /> <slot />
|
class="flex items-center gap-2"
|
||||||
</bitinflow-button>
|
:class="calculateClasses"
|
||||||
|
:to="to"
|
||||||
|
>
|
||||||
|
<i :class="['fal', icon]" />
|
||||||
|
<slot />
|
||||||
|
</bitinflow-button-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BitinflowButton from "./BitinflowButton.vue";
|
import BitinflowButtonLink from "./BitinflowButtonLink.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowSecondLevelLink",
|
name: "BitinflowSecondLevelLink",
|
||||||
components: {BitinflowButton},
|
components: {BitinflowButtonLink},
|
||||||
props: {
|
props: {
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'fa-arrow-up-right-from-square'
|
default: 'fa-arrow-up-right-from-square'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
calculateClasses: function () {
|
||||||
|
return {
|
||||||
|
'router-link-active': this.$route.path.includes(this.to),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.router-link-active, .router-link-exact-active {
|
||||||
|
@apply bg-primary-500 dark:bg-primary-500;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.router-link-active:hover, .router-link-exact-active:hover {
|
||||||
|
@apply bg-primary-600 dark:bg-primary-400;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,57 @@
|
|||||||
|
<!-- eslint-disable vue/require-explicit-emits -->
|
||||||
<template>
|
<template>
|
||||||
<nav
|
<nav
|
||||||
class="w-64 bg-white text-black shadow dark:bg-base-700 dark:text-white dark:border-base-900 dark:border-l flex flex-col overflow-y-auto h-screen absolute sm:relative transform -translate-x-full sm:translate-x-0 pt-10 pb-4 px-4 space-y-2">
|
class="w-64 bg-white text-black shadow dark:bg-base-700 dark:text-white dark:border-base-900 dark:border-l flex flex-col overflow-y-auto h-screen absolute sm:relative transform -translate-x-full sm:translate-x-0 pt-10 pb-4 px-4 space-y-2"
|
||||||
|
>
|
||||||
<span class="font-semibold px-4">
|
<span class="font-semibold px-4">
|
||||||
<slot name="title" />
|
<slot name="title" />
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col gap-2 justify-between flex-1">
|
<div class="flex flex-col gap-2 justify-between flex-1">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
|
||||||
|
<template v-if="loading">
|
||||||
|
<div
|
||||||
|
v-for="i in 3"
|
||||||
|
:key="i"
|
||||||
|
class="bg-zinc-100 dark:bg-base-600 rounded-lg px-4 py-2 h-10 animate-pulse"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div v-else-if="empty" class="bg-zinc-100 dark:bg-base-600 rounded-lg px-4 py-16 text-xl opacity-50 text-center">
|
||||||
|
<i class="fas fa-ghost mr-2 text-5xl opacity-70" />
|
||||||
<div>
|
<div>
|
||||||
<bitinflow-second-level-link
|
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> button below to create your first resource.
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
You don't have any resources yet.
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="hasCreateListener">
|
||||||
|
<bitinflow-second-level-button
|
||||||
class="bg-zinc-100 hover:bg-zinc-200 dark:bg-base-500 dark:hover:bg-base-600"
|
class="bg-zinc-100 hover:bg-zinc-200 dark:bg-base-500 dark:hover:bg-base-600"
|
||||||
icon="fa-plus"
|
icon="fa-plus"
|
||||||
|
@click="$emit('create')"
|
||||||
>
|
>
|
||||||
Create Resource
|
Create Resource
|
||||||
</bitinflow-second-level-link>
|
</bitinflow-second-level-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BitinflowSecondLevelLink from "./BitinflowSecondLevelLink.vue";
|
import BitinflowSecondLevelButton from "./BitinflowSecondLevelButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowSecondLevelMenu",
|
name: "BitinflowSecondLevelMenu",
|
||||||
components: {BitinflowSecondLevelLink},
|
components: {BitinflowSecondLevelButton},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -33,8 +59,22 @@ export default {
|
|||||||
default: [
|
default: [
|
||||||
{name: 'Test', href: 'Test'}
|
{name: 'Test', href: 'Test'}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasCreateListener() {
|
||||||
|
return this.$attrs && this.$attrs.onCreate;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
44
src/runtime/components/BitinflowSelectBox.vue
Normal file
44
src/runtime/components/BitinflowSelectBox.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-base-700 border-2 rounded p-4 relative"
|
||||||
|
:class="[selected ? 'border-primary-500' : 'dark:border-base-600']"
|
||||||
|
>
|
||||||
|
<div class="absolute top-[-.6rem] left-[-.6rem] flex bg-primary-500 rounded">
|
||||||
|
<div
|
||||||
|
v-if="selected"
|
||||||
|
class="w-4 h-4 flex justify-center"
|
||||||
|
>
|
||||||
|
<i class="fas fa-check text-white text-xs text-primary-500 self-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
class="after:absolute after:inset-0"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="click"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowSelectBox",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['update:selected'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
this.$emit('update:selected', !this.selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
11
src/runtime/components/BitinflowSelectBoxGroup.vue
Normal file
11
src/runtime/components/BitinflowSelectBoxGroup.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-5 gap-4 text-center">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowSelectBox"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
199
src/runtime/components/BitinflowTable.vue
Normal file
199
src/runtime/components/BitinflowTable.vue
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="loading">
|
||||||
|
<div
|
||||||
|
v-for="i in 3"
|
||||||
|
:key="i"
|
||||||
|
class="bg-white text-black dark:bg-base-700 rounded-lg h-16 animate-pulse"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
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"/>
|
||||||
|
<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>
|
||||||
|
button below to create your first resource.
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
You don't have any resources yet.
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasCreateListener">
|
||||||
|
<bitinflow-button
|
||||||
|
color="light"
|
||||||
|
variant="outline"
|
||||||
|
class="mt-8"
|
||||||
|
size="sm"
|
||||||
|
icon="fa-plus"
|
||||||
|
@click="$emit('create')"
|
||||||
|
>
|
||||||
|
Create Resource
|
||||||
|
</bitinflow-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="text-black dark:text-white"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4 py-2">
|
||||||
|
<div>
|
||||||
|
<bitinflow-table-checkbox
|
||||||
|
:model-value="selectedItems.length === items.length"
|
||||||
|
@update:model-value="selectAll"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
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>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<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
|
||||||
|
name="row"
|
||||||
|
:item="item"
|
||||||
|
:keys="keys"
|
||||||
|
/>
|
||||||
|
</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="clickOption(option)"
|
||||||
|
>
|
||||||
|
{{ 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: {BitinflowTableFloatingAction, BitinflowTableCheckbox, BitinflowTableRow},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keys: Object.keys(this.columns),
|
||||||
|
checkedItems: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
gridClass() {
|
||||||
|
return `grid grid-cols-${this.keys.length} gap-4`;
|
||||||
|
},
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
clickOption(option) {
|
||||||
|
option.action(this.selectedItems)
|
||||||
|
this.selectAll(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
37
src/runtime/components/BitinflowTableCheckbox.vue
Normal file
37
src/runtime/components/BitinflowTableCheckbox.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<a
|
||||||
|
class="w-4 h-4 flex justify-center rounded p-2 text-black dark:text-white border-2 border-zinc-200 dark:border-base-600 dark:bg-base-700 dark:text-white"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="toggle"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
v-if="modelValue"
|
||||||
|
class="fas fa-check text-xs text-primary-500 self-center"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BitinflowTableCheckbox",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.$emit('update:modelValue', !this.modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
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>
|
||||||
103
src/runtime/components/BitinflowTableRow.vue
Normal file
103
src/runtime/components/BitinflowTableRow.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="self-center">
|
||||||
|
<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 z-0"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="click"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
:class="gridClass"
|
||||||
|
class="items-center flex-1 px-6 py-4"
|
||||||
|
>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-20 h-18 flex justify-center items-center">
|
||||||
|
<div class="w-1/2 h-11">
|
||||||
|
<bitinflow-dropdown ref="dropdown">
|
||||||
|
<template #menu>
|
||||||
|
<template
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.name"
|
||||||
|
>
|
||||||
|
<bitinflow-dropdown-item
|
||||||
|
:destructive="option.destructive"
|
||||||
|
@click="optionClick(option)"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BitinflowTableCheckbox from "./BitinflowTableCheckbox.vue";
|
||||||
|
import BitinflowDropdown from "./BitinflowDropdown.vue";
|
||||||
|
import BitinflowDropdownItem from "./BitinflowDropdownItem.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BitinflowTableRow",
|
||||||
|
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', 'update:selected'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
select(value) {
|
||||||
|
this.$emit('update:selected', !value);
|
||||||
|
},
|
||||||
|
click() {
|
||||||
|
this.$emit('click');
|
||||||
|
},
|
||||||
|
optionClick(option) {
|
||||||
|
this.$refs.dropdown.close();
|
||||||
|
option.action([this.item])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.circle {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,27 +11,29 @@
|
|||||||
<nav class="bg-white dark:bg-base-700 py-4">
|
<nav class="bg-white dark:bg-base-700 py-4">
|
||||||
<div class="container mx-auto px-4 lg:px-16">
|
<div class="container mx-auto px-4 lg:px-16">
|
||||||
<div class="hidden xl:block space-x-4">
|
<div class="hidden xl:block space-x-4">
|
||||||
<bitinflow-button
|
<bitinflow-button-link
|
||||||
v-for="item in thirdLevelLinks"
|
v-for="item in thirdLevelLinks"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
|
:to="resolve(item.to)"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
:class="['far', item.icon]"
|
:class="['far', item.icon]"
|
||||||
class="icon"
|
class="icon"
|
||||||
/> {{ link.name }}
|
/> {{ item.name }}
|
||||||
</bitinflow-button>
|
</bitinflow-button-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col xl:hidden">
|
<div class="flex flex-col xl:hidden">
|
||||||
<select
|
<select
|
||||||
v-model="link"
|
v-model="link"
|
||||||
class="bg-white"
|
class="bg-white dark:bg-base-700 outline-none"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="item in thirdLevelLinks"
|
v-for="item in thirdLevelLinks"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:value="item.to"
|
:value="resolve(item.to)"
|
||||||
|
:selected="link === item.to"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</option>
|
</option>
|
||||||
@@ -45,23 +47,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import {mapState} from "pinia";
|
import {mapState} from "pinia";
|
||||||
import {useMenuStore} from "../stores/menu";
|
import {useMenuStore} from "../stores/menu";
|
||||||
import BitinflowButton from "./BitinflowButton.vue";
|
import BitinflowButtonLink from "./BitinflowButtonLink.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BitinflowThirdLevelMenu",
|
name: "BitinflowThirdLevelMenu",
|
||||||
components: {BitinflowButton},
|
components: {BitinflowButtonLink},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
link: ''
|
link: this.$route.path
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useMenuStore, ['thirdLevelLinks'])
|
...mapState(useMenuStore, ['thirdLevelLinks', 'thirdLevelProps'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
this.link = event.target.value
|
this.link = event.target.value
|
||||||
this.$router.push(event.target.value)
|
this.$router.push(event.target.value)
|
||||||
|
},
|
||||||
|
resolve(to) {
|
||||||
|
// replace all :params with the actual values
|
||||||
|
return to.replace(/:([^/]+)/g, (_, param) => this.thirdLevelProps[param])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,7 @@ export const useMenu = (options: MenuOptions) => {
|
|||||||
if (options.thirdLevelLinks) {
|
if (options.thirdLevelLinks) {
|
||||||
menu.updateThirdLevelLinks(options.thirdLevelLinks)
|
menu.updateThirdLevelLinks(options.thirdLevelLinks)
|
||||||
}
|
}
|
||||||
|
if (options.thirdLevelProps) {
|
||||||
|
menu.updateThirdLevelProps(options.thirdLevelProps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ import {ThirdLevelLink} from "../../types";
|
|||||||
export const useMenuStore = defineStore('menu', {
|
export const useMenuStore = defineStore('menu', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
thirdLevelLinks: [] as Array<ThirdLevelLink>
|
thirdLevelLinks: [] as Array<ThirdLevelLink>,
|
||||||
|
thirdLevelProps: [] as Object,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
updateThirdLevelLinks(links: Array<ThirdLevelLink>) {
|
updateThirdLevelLinks(links: Array<ThirdLevelLink>) {
|
||||||
this.thirdLevelLinks = links;
|
this.thirdLevelLinks = links;
|
||||||
},
|
},
|
||||||
|
updateThirdLevelProps(props: Object) {
|
||||||
|
this.thirdLevelProps = props;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export interface MenuOptions {
|
export interface MenuOptions {
|
||||||
thirdLevelLinks: Array<ThirdLevelLink>;
|
thirdLevelLinks: Array<ThirdLevelLink>;
|
||||||
|
thirdLevelProps: Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThirdLevelLink {
|
export interface ThirdLevelLink {
|
||||||
|
|||||||
Reference in New Issue
Block a user