32 Commits
v0.0.3 ... main

Author SHA1 Message Date
René Preuß
26b40d4c8c chore(release): v0.0.6 2023-04-13 21:45:18 +02:00
René Preuß
87bd79a661 Add exact link 2023-04-13 21:44:43 +02:00
René Preuß
378301d758 chore(release): v0.0.5 2023-04-13 21:38:07 +02:00
René Preuß
583aeacf37 Add support for href and darkmode toggle 2023-04-13 21:36:37 +02:00
René Preuß
56bd212ab6 Fix closing 2023-02-20 23:52:16 +01:00
René Preuß
389af82cc3 Fix closing 2023-02-20 23:50:00 +01:00
René Preuß
98c6326e32 Improve color 2023-02-20 23:47:22 +01:00
René Preuß
bf14028a02 Add dropdown (in-dev) 2023-02-20 23:46:29 +01:00
René Preuß
4dc1d0a704 Add placeholder element 2023-02-20 21:11:33 +01:00
René Preuß
d9041f471e New elements and updates 2023-02-20 18:42:21 +01:00
René Preuß
40d02e52ec Improve base colors 2023-02-20 14:12:51 +01:00
René Preuß
81df3fceb6 Color fixes 2023-02-20 13:59:34 +01:00
René Preuß
7ed407119b Color fixes 2023-02-20 13:48:30 +01:00
René Preuß
567ed71dbe Color fixes 2023-02-20 13:32:09 +01:00
73ba21634e remove header title/action
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-20 12:54:38 +01:00
6942976175 merge branch 'main' of github.com:bitinflow/ui 2023-02-20 12:40:54 +01:00
René Preuß
dda0cf2df5 Add loading state 2023-02-20 12:37:21 +01:00
René Preuß
ba9aa2e1b2 Color tuning 2023-02-20 12:33:18 +01:00
René Preuß
9dab7e782e Responsiveness and colors 2023-02-19 22:34:19 +01:00
69663ba7fa add header
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-19 22:22:13 +01:00
René Preuß
15fee4c0b4 Fix base colors 2023-02-19 21:55:12 +01:00
René Preuß
ae31a8b08b Merge branch 'main' of github.com:bitinflow/ui 2023-02-19 21:46:47 +01:00
René Preuß
bd3b24f2de Add new components 2023-02-19 21:46:28 +01:00
38af9291cd update button, card and alert
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-19 21:46:18 +01:00
René Preuß
8ae2b9587c Add new components 2023-02-19 16:52:20 +01:00
René Preuß
e4b4354a65 Fix dummy 2023-02-19 12:19:46 +01:00
René Preuß
f3711e2c83 Apply to all bitinflow logos 2023-02-19 12:17:51 +01:00
René Preuß
fb166d4d7e Add easter egg 2023-02-19 12:14:33 +01:00
René Preuß
7bf7a1273a Add avatar and ui fix 2023-02-19 11:12:50 +01:00
René Preuß
b40848aee2 Ui fixes and new features 2023-02-18 23:03:43 +01:00
René Preuß
c0a53a7864 chore(release): v0.0.4 2023-02-17 20:33:25 +01:00
René Preuß
71d836e5c4 Cleanup 2023-02-17 20:25:31 +01:00
72 changed files with 6444 additions and 692 deletions

2
.gitignore vendored
View File

@@ -14,7 +14,7 @@ node_modules
**/.yarn/*state* **/.yarn/*state*
# Generated dirs # Generated dirs
# dist dist
# Nuxt # Nuxt
.nuxt .nuxt

11
CHANGELOG.md Normal file
View File

@@ -0,0 +1,11 @@
# Changelog
## v0.0.6
## v0.0.5
## v0.0.4
## v0.0.3

5
dist/module.cjs vendored
View File

@@ -1,5 +0,0 @@
module.exports = function(...args) {
return import('./module.mjs').then(m => m.default.call(this, ...args))
}
const _meta = module.exports.meta = require('./module.json')
module.exports.getMeta = () => Promise.resolve(_meta)

7
dist/module.d.ts vendored
View File

@@ -1,7 +0,0 @@
import * as _nuxt_schema from '@nuxt/schema';
interface ModuleOptions {
}
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
export { ModuleOptions, _default as default };

5
dist/module.json vendored
View File

@@ -1,5 +0,0 @@
{
"name": "@bitinflow/ui",
"configKey": "ui",
"version": "0.0.2"
}

28
dist/module.mjs vendored
View File

@@ -1,28 +0,0 @@
import { defineNuxtModule, useLogger, createResolver, addPlugin, addImportsDir, addComponentsDir } from '@nuxt/kit';
const PACKAGE_NAME = "ui";
const module = defineNuxtModule({
meta: {
name: `@bitinflow/${PACKAGE_NAME}`,
configKey: "ui"
},
// Default configuration options of the Nuxt module
defaults: {},
setup(options, nuxt) {
const logger = useLogger(PACKAGE_NAME);
const resolver = createResolver(import.meta.url);
addPlugin(resolver.resolve("./runtime/plugin"));
const composables = resolver.resolve("./runtime/composables");
addImportsDir(composables);
const components = resolver.resolve("./runtime/components");
addComponentsDir({
path: components,
watch: true
}).then((r) => {
console.log(r);
});
logger.success("@bitinflow/ui module loaded");
}
});
export { module as default };

View File

@@ -1,8 +0,0 @@
declare namespace _default {
const name: string;
const emits: string[];
namespace methods {
function click(): void;
}
}
export default _default;

View File

@@ -1,15 +0,0 @@
<template>
<div class="bg-white text-black dark:bg-base-700 dark:text-white rounded shadow mb-8">
<slot />
</div>
</template>
<script>
export default {
name: "BitinflowCard"
}
</script>
<style scoped>
</style>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,17 +0,0 @@
<template>
<div class="py-4 px-8 border-b dark:border-base-800 rounded-t">
<div class="text-xl font-medium">
<slot />
</div>
</div>
</template>
<script>
export default {
name: "BitinflowCardHeader"
}
</script>
<style scoped>
</style>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,20 +0,0 @@
<template>
<a class="hover:bg-primary-500 rounded-lg text-center text-xs py-4 flex flex-col space-y-2" href="#">
<i :class="['fal text-xl', icon]"></i>
<span>
<slot />
</span>
</a>
</template>
<script>
export default {
name: "BitinflowFirstLevelLink",
props: {
icon: {
type: String,
default: 'fa-arrow-up-right-from-square'
}
}
}
</script>

View File

@@ -1,11 +0,0 @@
declare namespace _default {
const name: string;
namespace props {
namespace icon {
export const type: StringConstructor;
const _default: string;
export { _default as default };
}
}
}
export default _default;

View File

@@ -1,144 +0,0 @@
<template>
<div class="bg-gray-100 dark:bg-base min-h-screen flex flex-col relative">
<!-- Overlay -->
<div
ref="overlay"
class="sm:hidden hidden bg-gray-800 opacity-50 w-full min-h-screen absolute z-10"
/>
<!-- Header -->
<header class="bg-white relative shadow sm:hidden z-10">
<div class="container flex justify-between items-center py-4">
<img
src="/img/icon-light.svg"
class="h-4"
alt="bitinflow"
>
<div class="flex items-center space-x-4">
<nuxt-link
v-for="link in secondaryMenu"
:key="link.name"
:to="link.to"
>
<i
:class="link.icon"
class="icon"
/> {{ link.name }}
</nuxt-link>
<nuxt-link
class="inline-block flex"
to="/"
>
<img
class="h-8 w-8 rounded-full"
alt="profile"
src="/img/avatar.jpg"
/>
</nuxt-link>
<button
class="focus-within:outline-none pl-4"
@click="toggleMenu"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</header>
<!-- Sidenav with Content -->
<div class="flex flex-grow">
<!-- Sidenav -->
<nav
ref="sidebar"
class="flex h-screen flex-col w-36 bg-base-800 dark:border-r dark:border-base text-gray-100 shadow sm:relative absolute inset-y-0 transform -translate-x-full sm:translate-x-0 transition transition-transform z-20"
>
<!-- 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">
<button @click="toggleDarkMode">
<img
src="/img/icon-light.svg"
class="h-8 w-auto"
alt="Logo"
>
</button>
</div>
<div class="flex-auto flex flex-col overflow-y-auto py-6 space-y-6">
<!-- nav -->
<nav class="primary flex flex-grow flex-col space-y-4 px-6">
<slot name="top" />
</nav>
<nav class="flex flex-initial flex-col space-y-4 px-6">
<slot name="bottom" />
</nav>
</div>
<div class="flex-none" />
</nav>
<!-- Content -->
<main class="flex flex-1">
<slot />
</main>
</div>
</div>
</template>
<script>
export default {
name: "BitinflowFirstLevelMenu",
data() {
return {
primaryMenu: [
{name: 'Home', icon: 'fal fa-home', to: '/', exact: true},
{name: 'Buckets', icon: 'fal fa-bucket', to: '/buckets', exact: false},
{name: 'Domains', icon: 'fal fa-globe', to: '/domains', exact: false},
{name: 'Spaces', icon: 'fal fa-meteor', to: '/spaces', exact: false},
{name: 'Zones', icon: 'fal fa-list-ul', to: '/zones', exact: false},
],
secondaryMenu: [
{name: 'Logout', icon: 'fal fa-sign-out', to: '/logout'},
],
};
},
methods: {
toggleMenu() {
this.$refs.overlay.classList.toggle('hidden');
this.$refs.sidebar.classList.toggle('-translate-x-full');
},
closeMenu() {
this.$refs.overlay.classList.add('hidden');
this.$refs.sidebar.classList.add('-translate-x-full');
},
toggleDarkMode() {
document.body.classList.toggle('dark')
}
},
};
</script>
<style scoped>
.nuxt-link-active {
background: #00BFA5;
}
.nuxt-link-active:hover {
background: #004F44;
}
</style>

View File

@@ -1,22 +0,0 @@
declare namespace _default {
const name: string;
function data(): {
primaryMenu: {
name: string;
icon: string;
to: string;
exact: boolean;
}[];
secondaryMenu: {
name: string;
icon: string;
to: string;
}[];
};
namespace methods {
function toggleMenu(): void;
function closeMenu(): void;
function toggleDarkMode(): void;
}
}
export default _default;

View File

@@ -1,15 +0,0 @@
<template>
<div class="flex-auto">
<slot/>
</div>
</template>
<script>
export default {
name: "BitinflowFlex"
}
</script>
<style scoped>
</style>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,44 +0,0 @@
<template>
<div 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-primary-500 p-2 shadow-lg sm:p-3">
<div class="flex flex-wrap items-center justify-between">
<div class="flex w-0 flex-1 items-center">
<span class="flex rounded-lg bg-primary-800 p-2">
<!-- Heroicon name: outline/megaphone -->
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46" />
</svg>
</span>
<p class="ml-3 truncate font-medium text-white">
<span class="md:hidden">We announced a new product!</span>
<span class="hidden md:inline">Big news! We're excited to announce a brand new product.</span>
</p>
</div>
<div class="order-3 mt-2 w-full flex-shrink-0 sm:order-2 sm:mt-0 sm:w-auto">
<a href="#" class="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-2 text-sm font-medium text-primary-500 shadow-sm hover:bg-zinc-200">Learn more</a>
</div>
<div class="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
<button type="button" class="-mr-1 flex rounded-md p-2 hover:bg-primary-500 focus:outline-none focus:ring-2 focus:ring-white">
<span class="sr-only">Dismiss</span>
<!-- Heroicon name: outline/x-mark -->
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "BitinflowFloatingBanner"
}
</script>
<style scoped>
</style>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,11 +0,0 @@
<template>
<div class="bg-gradient-to-tr from-primary-600 to-primary-400 text-white rounded shadow p-8">
<slot />
</div>
</template>
<script>
export default {
name: "BitinflowHeroCard"
}
</script>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,11 +0,0 @@
<template>
<div class="overflow-y-auto h-screen">
<slot />
</div>
</template>
<script>
export default {
name: "BitinflowScreenScrollContainer"
}
</script>

View File

@@ -1,4 +0,0 @@
declare namespace _default {
const name: string;
}
export default _default;

View File

@@ -1,14 +0,0 @@
declare namespace _default {
const name: string;
namespace components {
export { BitinflowButton };
}
namespace props {
namespace icon {
export const type: StringConstructor;
const _default: string;
export { _default as default };
}
}
}
export default _default;

View File

@@ -1,49 +0,0 @@
<template>
<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">
<span class="font-semibold px-4">
<slot name="title" />
</span>
<div class="flex flex-col gap-2 justify-between flex-1">
<div class="flex flex-col gap-2">
<slot />
</div>
<div>
<bitinflow-second-level-link
class="bg-zinc-100 hover:bg-zinc-200 dark:bg-base-500 dark:hover:bg-base-600"
icon="fa-plus"
>
Create Resource
</bitinflow-second-level-link>
</div>
</div>
</nav>
</template>
<script>
import BitinflowSecondLevelLink from "./BitinflowSecondLevelLink.vue";
export default {
name: "BitinflowSecondLevelMenu",
components: {BitinflowSecondLevelLink},
props: {
items: {
type: Array,
// eslint-disable-next-line vue/require-valid-default-prop
default: [
{name: 'Test', href: 'Test'}
]
}
}
}
</script>
<style scoped>
.nuxt-link-active {
background: #f3f3f3;
}
.dark .nuxt-link-active {
background: #464649;
}
</style>

View File

@@ -1,17 +0,0 @@
declare namespace _default {
const name: string;
namespace components {
export { BitinflowSecondLevelLink };
}
namespace props {
namespace items {
export const type: ArrayConstructor;
const _default: {
name: string;
href: string;
}[];
export { _default as default };
}
}
}
export default _default;

View File

@@ -1,78 +0,0 @@
<template>
<div class="flex-1 dark:border-l dark:border-base shadow text-black dark:text-white">
<div class="bg-white border-b dark:bg-base-600 dark:border-base">
<div class="container mx-auto px-4 lg:px-16 py-10">
<div class="text-3xl font-semibold">
<slot name="title" />
</div>
</div>
</div>
<nav class="bg-white dark:bg-base-700 py-4">
<div class="container mx-auto px-4 lg:px-16">
<div class="hidden xl:block space-x-4">
<bitinflow-button
v-for="item in thirdLevelLinks"
:key="item.name"
>
<i
:class="['far', item.icon]"
class="icon"
/> {{ link.name }}
</bitinflow-button>
</div>
<div class="flex flex-col xl:hidden">
<select
v-model="link"
class="bg-white"
@change="onChange"
>
<option
v-for="item in thirdLevelLinks"
:key="item.name"
:value="item.to"
>
{{ item.name }}
</option>
</select>
</div>
</div>
</nav>
</div>
</template>
<script>
import {mapState} from "pinia";
import {useMenuStore} from "../stores/menu.js";
import BitinflowButton from "./BitinflowButton.vue";
export default {
name: "BitinflowThirdLevelMenu",
components: {BitinflowButton},
data() {
return {
link: ''
}
},
computed: {
...mapState(useMenuStore, ['thirdLevelLinks'])
},
methods: {
onChange(event) {
this.link = event.target.value
this.$router.push(event.target.value)
}
}
}
</script>
<style scoped>
.nuxt-link-active {
background: #f3f3f3;
}
.dark .nuxt-link-active {
background: #464649;
}
</style>

View File

@@ -1,20 +0,0 @@
declare namespace _default {
const name: string;
namespace components {
export { BitinflowButton };
}
function data(): {
link: string;
};
namespace computed {
const thirdLevelLinks: () => {
name: string;
icon: string;
to: string;
}[];
}
namespace methods {
function onChange(event: any): void;
}
}
export default _default;

View File

@@ -1,2 +0,0 @@
import { MenuOptions } from "../../types";
export declare const useMenu: (options: MenuOptions) => void;

View File

@@ -1,7 +0,0 @@
import { useMenuStore } from "../stores/menu.mjs";
export const useMenu = (options) => {
const menu = useMenuStore();
if (options.thirdLevelLinks) {
menu.updateThirdLevelLinks(options.thirdLevelLinks);
}
};

View File

@@ -1,2 +0,0 @@
declare const _default: any;
export default _default;

View File

@@ -1,4 +0,0 @@
import { defineNuxtPlugin } from "#app";
export default defineNuxtPlugin((nuxtApp) => {
console.log("Plugin injected by my-module!");
});

View File

@@ -1,6 +0,0 @@
import { ThirdLevelLink } from "../../types";
export declare const useMenuStore: import("pinia").StoreDefinition<"menu", {
thirdLevelLinks: ThirdLevelLink[];
}, {}, {
updateThirdLevelLinks(links: Array<ThirdLevelLink>): void;
}>;

View File

@@ -1,13 +0,0 @@
import { defineStore } from "pinia";
export const useMenuStore = defineStore("menu", {
state: () => {
return {
thirdLevelLinks: []
};
},
actions: {
updateThirdLevelLinks(links) {
this.thirdLevelLinks = links;
}
}
});

10
dist/types.d.ts vendored
View File

@@ -1,10 +0,0 @@
import { ModuleOptions } from './module'
declare module '@nuxt/schema' {
interface NuxtConfig { ['ui']?: Partial<ModuleOptions> }
interface NuxtOptions { ['ui']?: ModuleOptions }
}
export { ModuleOptions, default } from './module'

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bitinflow/ui", "name": "@bitinflow/ui",
"version": "0.0.3", "version": "0.0.6",
"description": "bitinflow UI Kit", "description": "bitinflow UI Kit",
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
@@ -22,7 +22,7 @@
"dev:build": "nuxi build playground", "dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags", "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
"release2": "npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags", "release2": "npm run test && npm run prepack && changelogen --release && npm publish --access public && git push --follow-tags",
"lint": "eslint .", "lint": "eslint .",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest watch" "test:watch": "vitest watch"
@@ -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",

View File

@@ -81,6 +81,8 @@
</template> </template>
<script> <script>
import {useMenu} from "../src/runtime/composables";
export default { export default {
setup() { setup() {
useMenu({ useMenu({

View File

@@ -23,9 +23,6 @@ export default defineNuxtModule<ModuleOptions>({
const logger = useLogger(PACKAGE_NAME) const logger = useLogger(PACKAGE_NAME)
const resolver = createResolver(import.meta.url) const resolver = createResolver(import.meta.url)
// Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack`
addPlugin(resolver.resolve('./runtime/plugin'))
const composables = resolver.resolve('./runtime/composables') const composables = resolver.resolve('./runtime/composables')
addImportsDir(composables) addImportsDir(composables)

View 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>

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

@@ -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>

View File

@@ -1,15 +1,23 @@
<template> <template>
<a <nuxt-link
href="#"
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="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 /> <slot />
</a> </nuxt-link>
</template> </template>
<script> <script>
export default { export default {
name: "BitinflowButton", name: "BitinflowButton",
props: {
to: {
type: String,
required: true
}
},
emits: ['click'], emits: ['click'],
methods: { methods: {

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="container mx-auto px-4 lg:px-16 py-8 space-y-8"> <div class="py-4 px-8 border-t dark:border-base-800 rounded-b">
<slot /> <slot />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "BitinflowContainer" name: "BitinflowCardFooter"
} }
</script> </script>

View 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>

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

@@ -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>

View File

@@ -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>

View File

@@ -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">
<img <button
src="/img/icon-light.svg" class="bitinflow-logo"
class="h-4" @click="toggleDarkMode"
alt="bitinflow"
> >
<img
src="https://cdn.bitinflow.com/ui/images/brand/icon-light.svg"
class="h-8 hidden dark:block"
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"
> >
@@ -82,29 +96,43 @@
<div class="flex-auto flex flex-col overflow-y-auto py-6 space-y-6"> <div class="flex-auto flex flex-col overflow-y-auto py-6 space-y-6">
<!-- nav --> <!-- nav -->
<nav class="primary flex flex-grow flex-col space-y-4 px-6"> <nav class="primary flex flex-grow flex-col space-y-4 px-6">
<slot name="top" /> <slot name="top"/>
</nav> </nav>
<nav class="flex flex-initial flex-col space-y-4 px-6"> <nav class="flex flex-initial flex-col space-y-4 px-6">
<slot name="bottom" /> <slot name="bottom"/>
</nav> </nav>
</div> </div>
<div class="flex-none" /> <div class="flex-none"/>
</nav> </nav>
<!-- Content --> <!-- Content -->
<main class="flex flex-1"> <main class="flex flex-1">
<slot /> <slot/>
</main> </main>
</div> </div>
</div> </div>
</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>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="p-8 rounded-b"> <div class="flex-1">
<slot /> <slot />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "BitinflowCardBody" name: "BitinflowFlex1"
} }
</script> </script>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="flex-auto"> <div class="flex-auto">
<slot/> <slot />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "BitinflowFlex" name: "BitinflowFlexAuto"
} }
</script> </script>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -1,6 +1,10 @@
<template> <template>
<bitinflow-button class="flex items-center gap-2"> <bitinflow-button
<i :class="['fal', icon]" /> <slot /> class="flex items-center gap-2 w-full"
icon="fa-plus"
@click="$emit('click')"
>
<slot />
</bitinflow-button> </bitinflow-button>
</template> </template>
@@ -15,6 +19,7 @@ export default {
type: String, type: String,
default: 'fa-arrow-up-right-from-square' default: 'fa-arrow-up-right-from-square'
} }
} },
emits: ['click']
} }
</script> </script>

View File

@@ -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>

View File

@@ -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 />
<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>
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>
<div> <div v-if="hasCreateListener">
<bitinflow-second-level-link <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>

View 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>

View 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>

View 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>

View 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>

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

@@ -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>

View File

@@ -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>
@@ -44,24 +46,28 @@
<script> <script>
import {mapState} from "pinia"; import {mapState} from "pinia";
import {useMenuStore} from "../stores/menu.js"; 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])
} }
} }
} }

View File

@@ -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)
}
} }

View File

@@ -1,5 +0,0 @@
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
console.log('Plugin injected by my-module!')
})

View File

@@ -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;
}
}, },
}) })

View File

@@ -1,5 +1,6 @@
export interface MenuOptions { export interface MenuOptions {
thirdLevelLinks: Array<ThirdLevelLink>; thirdLevelLinks: Array<ThirdLevelLink>;
thirdLevelProps: Object;
} }
export interface ThirdLevelLink { export interface ThirdLevelLink {

5301
yarn.lock Normal file

File diff suppressed because it is too large Load Diff