feat: remove vuetify

This commit is contained in:
Alisa | side.one 2023-05-28 01:36:42 +08:00
parent bfbe241ff4
commit 36fe08ec9f
243 changed files with 354 additions and 12890 deletions

43
components.d.ts vendored
View File

@ -1,48 +1,13 @@
// generated by unplugin-vue-components // generated by unplugin-vue-components
// We suggest you to commit this file into source control // We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core' import '@vue/runtime-core';
export {} export {};
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AddEditAddressDialog: typeof import('./src/plugins/vuetify/@core/components/AddEditAddressDialog.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'];
AppBarSearch: typeof import('./src/plugins/vuetify/@core/components/AppBarSearch.vue')['default'] RouterView: typeof import('vue-router')['RouterView'];
AppCardActions: typeof import('./src/plugins/vuetify/@core/components/AppCardActions.vue')['default']
AppCardCode: typeof import('./src/plugins/vuetify/@core/components/AppCardCode.vue')['default']
AppDateTimePicker: typeof import('./src/plugins/vuetify/@core/components/AppDateTimePicker.vue')['default']
AppDrawerHeaderSection: typeof import('./src/plugins/vuetify/@core/components/AppDrawerHeaderSection.vue')['default']
AppOtpInput: typeof import('./src/plugins/vuetify/@core/components/AppOtpInput.vue')['default']
AppPricing: typeof import('./src/plugins/vuetify/@core/components/AppPricing.vue')['default']
AppSearchHeader: typeof import('./src/plugins/vuetify/@core/components/AppSearchHeader.vue')['default']
BuyNow: typeof import('./src/plugins/vuetify/@core/components/BuyNow.vue')['default']
CardAddEditDialog: typeof import('./src/plugins/vuetify/@core/components/CardAddEditDialog.vue')['default']
CardStatisticsHorizontal: typeof import('./src/plugins/vuetify/@core/components/CardStatisticsHorizontal.vue')['default']
CardStatisticsVertical: typeof import('./src/plugins/vuetify/@core/components/CardStatisticsVertical.vue')['default']
CardStatisticsWithImages: typeof import('./src/plugins/vuetify/@core/components/CardStatisticsWithImages.vue')['default']
ConfirmDialog: typeof import('./src/plugins/vuetify/@core/components/ConfirmDialog.vue')['default']
CustomCheckboxes: typeof import('./src/plugins/vuetify/@core/components/CustomCheckboxes.vue')['default']
CustomCheckboxesWithIcon: typeof import('./src/plugins/vuetify/@core/components/CustomCheckboxesWithIcon.vue')['default']
CustomCheckboxesWithImage: typeof import('./src/plugins/vuetify/@core/components/CustomCheckboxesWithImage.vue')['default']
CustomizerSection: typeof import('./src/plugins/vuetify/@core/components/CustomizerSection.vue')['default']
CustomRadios: typeof import('./src/plugins/vuetify/@core/components/CustomRadios.vue')['default']
CustomRadiosWithIcon: typeof import('./src/plugins/vuetify/@core/components/CustomRadiosWithIcon.vue')['default']
CustomRadiosWithImage: typeof import('./src/plugins/vuetify/@core/components/CustomRadiosWithImage.vue')['default']
DialogCloseBtn: typeof import('./src/plugins/vuetify/@core/components/DialogCloseBtn.vue')['default']
EnableOneTimePasswordDialog: typeof import('./src/plugins/vuetify/@core/components/EnableOneTimePasswordDialog.vue')['default']
ErrorHeader: typeof import('./src/plugins/vuetify/@core/components/ErrorHeader.vue')['default']
I18n: typeof import('./src/plugins/vuetify/@core/components/I18n.vue')['default']
MoreBtn: typeof import('./src/plugins/vuetify/@core/components/MoreBtn.vue')['default']
Notifications: typeof import('./src/plugins/vuetify/@core/components/Notifications.vue')['default']
PricingPlanDialog: typeof import('./src/plugins/vuetify/@core/components/PricingPlanDialog.vue')['default']
ReferAndEarnDialog: typeof import('./src/plugins/vuetify/@core/components/ReferAndEarnDialog.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Shortcuts: typeof import('./src/plugins/vuetify/@core/components/Shortcuts.vue')['default']
TheCustomizer: typeof import('./src/plugins/vuetify/@core/components/TheCustomizer.vue')['default']
ThemeSwitcher: typeof import('./src/plugins/vuetify/@core/components/ThemeSwitcher.vue')['default']
UserInfoEditDialog: typeof import('./src/plugins/vuetify/@core/components/UserInfoEditDialog.vue')['default']
UserUpgradePlanDialog: typeof import('./src/plugins/vuetify/@core/components/UserUpgradePlanDialog.vue')['default']
} }
} }

View File

@ -19,171 +19,171 @@ const blockchain = useBlockchain();
const current = ref(''); const current = ref('');
blockchain.$subscribe((m, s) => { blockchain.$subscribe((m, s) => {
if (current.value != s.chainName) { if (current.value != s.chainName) {
current.value = s.chainName; current.value = s.chainName;
blockchain.initial(); blockchain.initial();
} }
}); });
const sidebarShow = ref(false); const sidebarShow = ref(false);
const sidebarOpen = ref(true); const sidebarOpen = ref(true);
const changeOpen = (index: Number) => { const changeOpen = (index: Number) => {
if (index === 0) { if (index === 0) {
sidebarOpen.value = !sidebarOpen.value; sidebarOpen.value = !sidebarOpen.value;
} }
}; };
const showDiscord = window.location.host.search('ping.pub') > -1; const showDiscord = window.location.host.search('ping.pub') > -1;
</script> </script>
<template> <template>
<div class="bg-gray-100 dark:bg-[#171d30]"> <div class="bg-gray-100 dark:bg-[#171d30]">
<!-- sidebar --> <!-- sidebar -->
<div <div class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700"
class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700" :class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }">
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"> <div class="flex items-center pl-4 py-4 mb-1">
<div class="flex items-center pl-4 py-4 mb-1"> <img class="w-10 h-10" src="../../assets/logo.svg" />
<img class="w-10 h-10" src="../../assets/logo.svg" /> <h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white"> Ping.pub
Ping.pub </h1>
</h1> <div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false">
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false"> <Icon icon="mdi-close" class="text-3xl" />
<Icon icon="mdi-close" class="text-3xl" />
</div>
</div>
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
<div v-if="item?.title && item?.children?.length" :tabindex="index" class="collapse" :class="{
'collapse-arrow': item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}">
<input type="checkbox" class="cursor-pointer" @click="changeOpen(index)" />
<div
class="collapse-title px-4 flex items-center h-12 cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
{{ item?.badgeContent }}
</div>
</div>
<div class="collapse-content">
<div class="menu bg-base-100 w-full">
<RouterLink v-for="(el, key) of item?.children" @click="sidebarShow = false" :key="key"
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:to="el?.to" :class="{
'!bg-primary':
$route.path === el?.to?.path && item?.title !== 'Favorite',
}">
<Icon v-if="!el?.icon?.image" icon="mdi:chevron-right" class="mr-2 ml-3" :class="{
'text-white': $route.path === el?.to?.path &&
item?.title !== 'Favorite',
}" />
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4" />
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}">
{{ $t(el?.title) }}
</div> </div>
</RouterLink>
</div> </div>
</div> <div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
</div> <div v-if="item?.title && item?.children?.length" :tabindex="index" class="collapse" :class="{
'collapse-arrow': item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}">
<input type="checkbox" class="cursor-pointer" @click="changeOpen(index)" />
<div
class="collapse-title px-4 flex items-center h-12 cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
{{ item?.badgeContent }}
</div>
</div>
<div class="collapse-content">
<div class="menu bg-base-100 w-full">
<RouterLink v-for="(el, key) of item?.children" @click="sidebarShow = false" :key="key"
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:to="el?.to" :class="{
'!bg-primary':
$route.path === el?.to?.path && item?.title !== 'Favorite',
}">
<Icon v-if="!el?.icon?.image" icon="mdi:chevron-right" class="mr-2 ml-3" :class="{
'text-white': $route.path === el?.to?.path &&
item?.title !== 'Favorite',
}" />
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4" />
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}">
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
</div>
</RouterLink>
</div>
</div>
</div>
<RouterLink :to="item?.to" v-if="item?.title && !item?.children?.length && item?.to" @click="sidebarShow = false" <RouterLink :to="item?.to" v-if="item?.title && !item?.children?.length && item?.to"
class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"> @click="sidebarShow = false"
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{ class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
'text-yellow-500': item?.title === 'Favorite', <Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-blue-500': item?.title !== 'Favorite', 'text-yellow-500': item?.title === 'Favorite',
}" /> 'text-blue-500': item?.title !== 'Favorite',
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" /> }" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200"> <img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
{{ item?.title }} <div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
</div> {{ item?.title }}
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass"> </div>
{{ item?.badgeContent }} <div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
</div> {{ item?.badgeContent }}
</RouterLink> </div>
<div v-if="item?.heading" class="px-4 text-sm pt-3 text-gray-400 pb-2 uppercase"> </RouterLink>
{{ item?.heading }} <div v-if="item?.heading" class="px-4 text-sm pt-3 text-gray-400 pb-2 uppercase">
</div> {{ item?.heading }}
</div> </div>
<div class="px-2"> </div>
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase"> <div class="px-2">
Sponsors <div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">
</div> Sponsors
<a href="https://osmosis.zone" </div>
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> <a href="https://osmosis.zone"
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" /> class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" />
Osmosis <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
</div> Osmosis
</a> </div>
<a href="https://becole.com" </a>
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> <a href="https://becole.com"
<img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3" /> class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3" />
Becole <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
</div> Becole
</a> </div>
</a>
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">Links</div> <div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">Links</div>
<a href="https://twitter.com/ping_pub" <a href="https://twitter.com/ping_pub"
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:twitter" class="text-xl mr-2" /> <Icon icon="mdi:twitter" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
Twitter Twitter
</div> </div>
</a> </a>
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW" <a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW"
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:discord" class="text-xl mr-2" /> <Icon icon="mdi:discord" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
Discord Discord
</div> </div>
</a> </a>
<a href="https://github.com/ping-pub/explorer/discussions" <a href="https://github.com/ping-pub/explorer/discussions"
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"> class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" /> <Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"> <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
FAQ FAQ
</div> </div>
</a> </a>
</div> </div>
</div>
<div class="xl:!ml-64 px-5 pt-4">
<!-- header -->
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow">
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
<Icon icon="mdi-menu" />
</div>
<ChainProfile />
<div class="flex-1 w-0"></div>
<!-- <NavSearchBar />-->
<NavBarI18n class="hidden md:!inline-block" />
<NavbarThemeSwitcher class="!inline-block" />
<NavBarWallet />
</div>
<!-- 👉 Pages -->
<RouterView v-slot="{ Component }">
<Transition mode="out-in">
<Component :is="Component" />
</Transition>
</RouterView>
<newFooter />
</div>
</div> </div>
<div class="xl:!ml-64 px-5 pt-4">
<!-- header -->
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow">
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
<Icon icon="mdi-menu" />
</div>
<ChainProfile />
<div class="flex-1 w-0"></div>
<!-- <NavSearchBar />-->
<NavBarI18n class="hidden md:!inline-block" />
<NavbarThemeSwitcher class="!inline-block" />
<NavBarWallet />
</div>
<!-- 👉 Pages -->
<RouterView v-slot="{ Component }">
<Transition mode="out-in">
<Component :is="Component" />
</Transition>
</RouterView>
<newFooter />
</div>
</div>
</template> </template>

View File

@ -2,12 +2,11 @@
import { useWasmStore } from '../WasmStore'; import { useWasmStore } from '../WasmStore';
import { ref } from 'vue'; import { ref } from 'vue';
import type { import type {
ContractInfo, ContractInfo,
PaginabledContractStates, PaginabledContractStates,
PaginabledContracts, PaginabledContracts,
} from '../types'; } from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import type { CustomInputContent } from '@/plugins/vuetify/@core/types';
import { useFormatter, useTxDialog } from '@/stores'; import { useFormatter, useTxDialog } from '@/stores';
import PaginationBar from '@/components/PaginationBar.vue'; import PaginationBar from '@/components/PaginationBar.vue';
import { PageRequest } from '@/types'; import { PageRequest } from '@/types';
@ -19,84 +18,82 @@ const response = ref({} as PaginabledContracts);
const wasmStore = useWasmStore(); const wasmStore = useWasmStore();
function loadContract(pageNum: number) { function loadContract(pageNum: number) {
const pr = new PageRequest() const pr = new PageRequest()
pr.setPage(pageNum) pr.setPage(pageNum)
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => { wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x; response.value = x;
}); });
} }
loadContract(1) loadContract(1)
const dialog = useTxDialog() const dialog = useTxDialog()
const format = useFormatter(); const format = useFormatter();
const infoDialog = ref(false); const infoDialog = ref(false);
const stateDialog = ref(false);
const queryDialog = ref(false);
const info = ref({} as ContractInfo); const info = ref({} as ContractInfo);
const state = ref({} as PaginabledContractStates); const state = ref({} as PaginabledContractStates);
const selected = ref(''); const selected = ref('');
function showInfo(address: string) { function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => { wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info; info.value = x.contract_info;
}); });
} }
function showState(address: string) { function showState(address: string) {
selected.value = address selected.value = address
pageload(1) pageload(1)
} }
function pageload(p: number) { function pageload(p: number) {
pageRequest.value.setPage(p) pageRequest.value.setPage(p)
wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => { wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => {
state.value = x; state.value = x;
}); });
} }
function showQuery(address: string) { function showQuery(address: string) {
selected.value = address; selected.value = address;
query.value = ''; query.value = '';
result.value = ''; result.value = '';
} }
function queryContract() { function queryContract() {
try { try {
if (selectedRadio.value === 'raw') { if (selectedRadio.value === 'raw') {
wasmStore.wasmClient wasmStore.wasmClient
.getWasmContractRawQuery(selected.value, query.value) .getWasmContractRawQuery(selected.value, query.value)
.then((x) => { .then((x) => {
result.value = JSON.stringify(x); result.value = JSON.stringify(x);
}) })
.catch((err) => { .catch((err) => {
result.value = JSON.stringify(err); result.value = JSON.stringify(err);
}); });
} else { } else {
wasmStore.wasmClient wasmStore.wasmClient
.getWasmContractSmartQuery(selected.value, query.value) .getWasmContractSmartQuery(selected.value, query.value)
.then((x) => { .then((x) => {
result.value = JSON.stringify(x); result.value = JSON.stringify(x);
}) })
.catch((err) => { .catch((err) => {
result.value = JSON.stringify(err); result.value = JSON.stringify(err);
}); });
}
} catch (err) {
result.value = JSON.stringify(err); // not works for now
} }
} catch (err) { // TODO, show error in the result.
result.value = JSON.stringify(err); // not works for now
}
// TODO, show error in the result.
} }
const radioContent: CustomInputContent[] = [ const radioContent = [
{ {
title: 'Raw Query', title: 'Raw Query',
desc: 'Return raw result', desc: 'Return raw result',
value: 'raw', value: 'raw',
}, },
{ {
title: 'Smart Query', title: 'Smart Query',
desc: 'Return structure result if possible', desc: 'Return structure result if possible',
value: 'smart', value: 'smart',
}, },
]; ];
const selectedRadio = ref('raw'); const selectedRadio = ref('raw');
@ -104,125 +101,130 @@ const query = ref('');
const result = ref(''); const result = ref('');
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full"> <h2 class="card-title truncate w-full">
Contract List of Code: {{ props.code_id }} Contract List of Code: {{ props.code_id }}
</h2> </h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table table-compact w-full mt-4"> <table class="table table-compact w-full mt-4">
<thead> <thead>
<tr> <tr>
<th style="position: relative; z-index: 2">Contract List</th> <th style="position: relative; z-index: 2">Contract List</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(v, index) in response.contracts" :key="index" class="hover"> <tr v-for="(v, index) in response.contracts" :key="index" class="hover">
<td>{{ v }}</td> <td>{{ v }}</td>
<td> <td>
<label @click="showInfo(v)" for="modal-contract-detail" <label @click="showInfo(v)" for="modal-contract-detail"
class="btn btn-primary btn-xs text-xs mr-2">contract</label> class="btn btn-primary btn-xs text-xs mr-2">contract</label>
<label class="btn btn-primary btn-xs text-xs mr-2" for="modal-contract-states" @click="showState(v)"> <label class="btn btn-primary btn-xs text-xs mr-2" for="modal-contract-states"
States @click="showState(v)">
</label> States
<label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2" @click="showQuery(v)"> </label>
Query <label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2"
</label> @click="showQuery(v)">
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs" Query
@click="dialog.open('wasm_execute_contract', { contract: v })"> </label>
Execute <label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs"
</label> @click="dialog.open('wasm_execute_contract', { contract: v })">
</td> Execute
</tr> </label>
</tbody> </td>
</table> </tr>
<div class="flex justify-between"> </tbody>
<PaginationBar :limit="50" :total="response.pagination?.total" :callback="loadContract" /> </table>
<label for="wasm_instantiate_contract" class="btn btn-primary my-5" <div class="flex justify-between">
@click="dialog.open('wasm_instantiate_contract', { codeId: props.code_id })">Instantiate Contract</label> <PaginationBar :limit="50" :total="response.pagination?.total" :callback="loadContract" />
</div> <label for="wasm_instantiate_contract" class="btn btn-primary my-5"
</div> @click="dialog.open('wasm_instantiate_contract', { codeId: props.code_id })">Instantiate
</div> Contract</label>
<input type="checkbox" id="modal-contract-detail" class="modal-toggle" />
<label for="modal-contract-detail" class="modal cursor-pointer">
<label class="modal-box relative p-2" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">Contract Detail</div>
<label @click="infoDialog = false" for="modal-contract-detail" class="btn btn-sm btn-circle"></label>
</div>
<div>
<DynamicComponent :value="info" />
</div>
</div>
</label>
</label>
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
<label for="modal-contract-states" class="modal cursor-pointer">
<label class="modal-box w-11/12 max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">Contract States</div>
<label @click="infoDialog = false" for="modal-contract-states" class="btn btn-sm btn-circle"></label>
</div>
<div class="overflow-auto">
<table class="table table-compact w-full text-sm">
<tr v-for="(v, index) in state.models" :key="index" class="hover">
<td class="" :data-tip="format.hexToString(v.key)">
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
</td>
<td class="text-left w-3/4" :title="format.base64ToString(v.value)">
{{ format.base64ToString(v.value) }}
</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageload" />
</div>
</div>
</label>
</label>
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
<label for="modal-contract-query" class="modal cursor-pointer">
<label class="modal-box w-11/12 max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">Query Contract</div>
<label @click="infoDialog = false" for="modal-contract-query" class="btn btn-sm btn-circle"></label>
</div>
<div class="px-3">
<div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent" :key="index"
:class="{ 'pt-2': index === 0 }">
<label class="label cursor-pointer justify-start" @click="selectedRadio = item?.value">
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4"
:checked="item?.value === selectedRadio" style="border: 1px solid #d2d6dc" />
<div>
<div class="text-base font-semibold">
{{ item?.title }}
</div>
<div class="text-xs">{{ item?.desc }}</div>
</div>
</label>
</div> </div>
</div>
<VTextarea v-model="query" label="Query String" class="my-2" />
<VTextarea v-model="result" label="Result" />
</div> </div>
<div class="mt-4 mb-4">
<button class="btn !btn-yes !border-yes px-4 text-white" @click="queryContract()">
Query Contract
</button>
</div>
</div>
</div> </div>
</label>
<input type="checkbox" id="modal-contract-detail" class="modal-toggle" />
<label for="modal-contract-detail" class="modal cursor-pointer">
<label class="modal-box relative p-2" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">Contract Detail</div>
<label @click="infoDialog = false" for="modal-contract-detail"
class="btn btn-sm btn-circle"></label>
</div>
<div>
<DynamicComponent :value="info" />
</div>
</div>
</label>
</label>
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
<label for="modal-contract-states" class="modal cursor-pointer">
<label class="modal-box w-11/12 max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">Contract States</div>
<label @click="infoDialog = false" for="modal-contract-states"
class="btn btn-sm btn-circle"></label>
</div>
<div class="overflow-auto">
<table class="table table-compact w-full text-sm">
<tr v-for="(v, index) in state.models" :key="index" class="hover">
<td class="" :data-tip="format.hexToString(v.key)">
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
</td>
<td class="text-left w-3/4" :title="format.base64ToString(v.value)">
{{ format.base64ToString(v.value) }}
</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageload" />
</div>
</div>
</label>
</label>
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
<label for="modal-contract-query" class="modal cursor-pointer">
<label class="modal-box w-11/12 max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">Query Contract</div>
<label @click="infoDialog = false" for="modal-contract-query"
class="btn btn-sm btn-circle"></label>
</div>
<div class="px-3">
<div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent"
:key="index" :class="{ 'pt-2': index === 0 }">
<label class="label cursor-pointer justify-start" @click="selectedRadio = item?.value">
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4"
:checked="item?.value === selectedRadio" style="border: 1px solid #d2d6dc" />
<div>
<div class="text-base font-semibold">
{{ item?.title }}
</div>
<div class="text-xs">{{ item?.desc }}</div>
</div>
</label>
</div>
</div>
<VTextarea v-model="query" label="Query String" class="my-2" />
<VTextarea v-model="result" label="Result" />
</div>
<div class="mt-4 mb-4">
<button class="btn !btn-yes !border-yes px-4 text-white" @click="queryContract()">
Query Contract
</button>
</div>
</div>
</div>
</label>
</label> </label>
</div> </div></template>
</template>

View File

@ -1,100 +0,0 @@
<script setup lang="ts">
import pixinventQr from '@images/pages/pixinvent-qr.png'
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
(e: 'submit', value: string): void
}
interface Props {
authCode?: string
isDialogVisible: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const authCode = ref(structuredClone(toRaw(props.authCode)))
const formSubmit = () => {
if (authCode.value) {
emit('submit', authCode.value)
emit('update:isDialogVisible', false)
}
}
const resetAuthCode = () => {
authCode.value = structuredClone(toRaw(props.authCode))
emit('update:isDialogVisible', false)
}
</script>
<template>
<VDialog
max-width="600"
:model-value="props.isDialogVisible"
@update:model-value="(val) => $emit('update:isDialogVisible', val)"
>
<VCard class="pa-5 pa-sm-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="resetAuthCode"
/>
<VCardItem>
<VCardTitle class="text-h5 font-weight-medium text-center">
Add Authenticator App
</VCardTitle>
</VCardItem>
<VCardText class="pt-6">
<h6 class="text-h6 font-weight-medium mb-3">
Authenticator Apps
</h6>
<p class="mb-6">
Using an authenticator app like Google Authenticator, Microsoft Authenticator, Authy, or 1Password, scan the QR code. It will generate a 6 digit code for you to enter below.
</p>
<div class="my-6">
<VImg
width="122"
:src="pixinventQr"
class="mx-auto"
/>
</div>
<VForm @submit.prevent="() => {}">
<VTextField
v-model="authCode"
name="auth-code"
label="Enter Authentication Code"
class="mb-4"
/>
<div class="flex justify-end flex-wrap gap-3">
<VBtn
color="secondary"
variant="tonal"
@click="resetAuthCode"
>
Cancel
</VBtn>
<VBtn
type="submit"
@click="formSubmit"
>
Continue
<VIcon
end
icon="mdi-chevron-right"
class="flip-in-rtl"
/>
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,261 +0,0 @@
<script setup lang="ts">
interface BillingAddress {
companyName: string
billingEmail: string
taxID: string
vatNumber: string
address: string
contact: string
country: string
state: string
zipCode: string
isSaveDefaultAddress: boolean
}
interface Props {
billingAddress?: BillingAddress
isDialogVisible: boolean
}
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
(e: 'submit', value: BillingAddress): void
}
const props = withDefaults(defineProps<Props>(), {
billingAddress: () => ({
companyName: '',
billingEmail: '',
taxID: '',
vatNumber: '',
address: '',
contact: '',
country: '',
state: '',
zipCode: '',
isSaveDefaultAddress: true,
}),
})
const emit = defineEmits<Emit>()
const billingAddress = ref<BillingAddress>(structuredClone(toRaw(props.billingAddress)))
const resetForm = () => {
emit('update:isDialogVisible', false)
billingAddress.value = structuredClone(toRaw(props.billingAddress))
}
const onFormSubmit = () => {
emit('update:isDialogVisible', false)
emit('submit', billingAddress.value)
}
const selectedAddress = ref('Home')
const addressTypes = [
{
icon: 'mdi-home-outline',
title: 'Home',
time: 'Delivery Time (7am - 9pm)',
},
{
icon: 'mdi-briefcase-outline',
title: 'Office',
time: 'Delivery Time (10am - 6pm)',
},
]
</script>
<template>
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 800"
:model-value="props.isDialogVisible"
@update:model-value="val => $emit('update:isDialogVisible', val)"
>
<VCard
v-if="props.billingAddress"
class="pa-sm-8 pa-5"
>
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="resetForm"
/>
<!-- 👉 Title -->
<VCardItem>
<VCardTitle class="text-h5 text-center">
{{ props.billingAddress.address ? 'Edit' : 'Add New' }} Address
</VCardTitle>
</VCardItem>
<VCardText class="pt-3">
<!-- 👉 Subtitle -->
<VCardSubtitle class="text-center mb-8">
Edit Address for future billing
</VCardSubtitle>
<VRow>
<VCol
v-for="type in addressTypes"
:key="type.title"
cols="12"
sm="6"
>
<div
class="custom-address-input border rounded cursor-pointer pa-4"
:class="selectedAddress === type.title ? 'bg-light-primary text-primary border-primary' : 'bg-light-secondary border-secondary'"
style="/* stylelint-disable-next-line max-empty-lines */
--v-border-opacity: 1;"
@click="selectedAddress = type.title"
>
<div class="flex align-center font-weight-medium gap-2 text-xl mb-1">
<VIcon
size="24"
:icon="type.icon"
/>
<span>{{ type.title }}</span>
</div>
<span>{{ type.time }}</span>
</div>
</VCol>
</VRow>
<!-- 👉 Form -->
<VForm
class="mt-4"
@submit.prevent="onFormSubmit"
>
<VRow>
<!-- 👉 Company Name -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.companyName"
label="Company Name"
/>
</VCol>
<!-- 👉 Email -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.billingEmail"
label="Email"
/>
</VCol>
<!-- 👉 Tax ID -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.taxID"
label="Tax ID"
/>
</VCol>
<!-- 👉 VAT Number -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.vatNumber"
label="VAT Number"
/>
</VCol>
<!-- 👉 Billing Address -->
<VCol cols="12">
<VTextarea
v-model="billingAddress.address"
rows="2"
label="Billing Address"
/>
</VCol>
<!-- 👉 Contact -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.contact"
label="Contact"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
>
<VSelect
v-model="billingAddress.country"
label="Country"
:items="['USA', 'Uk', 'France', 'Germany', 'Japan']"
/>
</VCol>
<!-- 👉 State -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.state"
label="State"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="billingAddress.zipCode"
label="Zip Code"
/>
</VCol>
<!-- 👉 default address -->
<VCol cols="12">
<VSwitch
v-model="billingAddress.isSaveDefaultAddress"
label="Make this default shipping address"
/>
</VCol>
<!-- 👉 Submit and Cancel button -->
<VCol
cols="12"
class="text-center"
>
<VBtn
type="submit"
class="me-3"
>
submit
</VBtn>
<VBtn
variant="tonal"
color="secondary"
@click="resetForm"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,378 +0,0 @@
<script setup lang="ts">
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { VList, VListItem, VListSubheader } from 'vuetify/components'
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
(e: 'update:searchQuery', value: string): void
(e: 'itemSelected', value: any): void
}
interface Suggestion {
icon: string
title: string
url: object
}
interface Suggestions {
title: string
content: Suggestion[]
}
interface Props {
isDialogVisible: boolean
searchQuery: string
searchResults: any[]
suggestions?: Suggestions[]
noDataSuggestion?: Suggestion[]
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
// 👉 Hotkey
const { ctrl_k, meta_k } = useMagicKeys()
const refSearchList = ref<VList>()
const searchQuery = ref(structuredClone(toRaw(props.searchQuery)))
const refSearchInput = ref<HTMLInputElement>()
const isLocalDialogVisible = ref(structuredClone(toRaw(props.isDialogVisible)))
const searchResults = ref(structuredClone(toRaw(props.searchResults)))
// 👉 Watching props change
watch(props, () => {
isLocalDialogVisible.value = structuredClone(toRaw(props.isDialogVisible))
searchResults.value = structuredClone(toRaw(props.searchResults))
searchQuery.value = structuredClone(toRaw(props.searchQuery))
})
// 👉 watching control + / to open dialog
watch([ctrl_k, meta_k], () => {
isLocalDialogVisible.value = true
emit('update:isDialogVisible', true)
})
// 👉 clear search result and close the dialog
const clearSearchAndCloseDialog = () => {
emit('update:isDialogVisible', false)
emit('update:searchQuery', '')
}
watchEffect(() => {
if (!searchQuery.value.length)
searchResults.value = []
})
// 👉 get fucus on search list
const getFocusOnSearchList = (e: KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
refSearchList.value?.focus('next')
}
else if (e.key === 'ArrowUp') {
e.preventDefault()
refSearchList.value?.focus('prev')
}
}
const dialogModelValueUpdate = (val: boolean) => {
emit('update:isDialogVisible', val)
emit('update:searchQuery', '')
}
// 👉 resolve categories name
const resolveCategories = (val: string) => {
if (val === 'dashboards')
return 'Dashboards'
if (val === 'appsPages')
return 'Apps & Pages'
if (val === 'userInterface')
return 'User Interface'
if (val === 'formsTables')
return 'Forms Tables'
if (val === 'chartsMisc')
return 'Charts Misc'
return 'Misc'
}
</script>
<template>
<VDialog
max-width="600"
:model-value="isLocalDialogVisible"
:height="$vuetify.display.smAndUp ? '550' : '100%'"
:fullscreen="$vuetify.display.width < 600"
class="app-bar-search-dialog"
@update:model-value="dialogModelValueUpdate"
@keyup.esc="clearSearchAndCloseDialog"
>
<VCard
height="100%"
width="100%"
class="position-relative"
>
<VCardText
class="pt-1"
style="max-height: 65px;"
>
<!-- 👉 Search Input -->
<VTextField
ref="refSearchInput"
v-model="searchQuery"
autofocus
variant="plain"
class="app-bar-autocomplete-box"
@keyup.esc="clearSearchAndCloseDialog"
@keydown="getFocusOnSearchList"
@update:model-value="$emit('update:searchQuery', searchQuery)"
>
<!-- 👉 Prepend Inner -->
<template #prepend-inner>
<VIcon
icon="mdi-magnify"
class="text-high-emphasis"
/>
</template>
<!-- 👉 Append Inner -->
<template #append-inner>
<div class="flex align-center mt-n1">
<div
class="text-base text-disabled cursor-pointer me-2"
@click="clearSearchAndCloseDialog"
>
[esc]
</div>
<IconBtn
size="x-small"
@click="clearSearchAndCloseDialog"
>
<VIcon icon="mdi-close" />
</IconBtn>
</div>
</template>
</VTextField>
</VCardText>
<!-- 👉 Divider -->
<VDivider />
<!-- 👉 Perfect Scrollbar -->
<PerfectScrollbar
:options="{ wheelPropagation: false, suppressScrollX: true }"
class="h-100"
>
<!-- 👉 Search List -->
<VList
v-show="searchQuery.length && !!searchResults.length"
ref="refSearchList"
density="compact"
class="app-bar-search-list"
>
<!-- 👉 list Item /List Sub header -->
<template
v-for="item in searchResults"
:key="item.title"
>
<VListSubheader
v-if="'header' in item"
class="text-disabled"
>
{{ resolveCategories(item.title) }}
</VListSubheader>
<template v-else>
<slot
name="searchResult"
:item="item"
>
<VListItem
link
@click="$emit('itemSelected', item)"
>
<template #prepend>
<VIcon
size="20"
:icon="item.icon"
class="me-3"
/>
</template>
<template #append>
<VIcon
size="20"
icon="mdi-subdirectory-arrow-left"
class="enter-icon text-disabled"
/>
</template>
<VListItemTitle>
{{ item.title }}
</VListItemTitle>
</VListItem>
</slot>
</template>
</template>
</VList>
<!-- 👉 Suggestions -->
<div
v-show="!!searchResults && !searchQuery"
class="h-100"
>
<slot name="suggestions">
<VCardText class="app-bar-search-suggestions h-100 pa-10">
<VRow
v-if="props.suggestions"
class="gap-y-4"
>
<VCol
v-for="suggestion in props.suggestions"
:key="suggestion.title"
cols="12"
sm="6"
class="ps-6"
>
<p class="text-xs text-disabled text-uppercase">
{{ suggestion.title }}
</p>
<VList class="card-list">
<VListItem
v-for="item in suggestion.content"
:key="item.title"
link
:title="item.title"
class="app-bar-search-suggestion"
@click="$emit('itemSelected', item)"
>
<template #prepend>
<VIcon
:icon="item.icon"
size="20"
class="me-2"
/>
</template>
</VListItem>
</VList>
</VCol>
</VRow>
</VCardText>
</slot>
</div>
<!-- 👉 No Data found -->
<div
v-show="!searchResults.length && searchQuery.length"
class="h-100"
>
<slot name="noData">
<VCardText class="h-100">
<div class="app-bar-search-suggestions flex flex-column align-center justify-center text-high-emphasis h-100">
<VIcon
size="75"
icon="mdi-file-remove-outline"
/>
<div class="flex align-center flex-wrap justify-center gap-2 text-h6 my-3">
<span>No Result For </span>
<span>"{{ searchQuery }}"</span>
</div>
<div
v-if="props.noDataSuggestion"
class="mt-8"
>
<span class="flex justify-center text-disabled">Try searching for</span>
<h6
v-for="suggestion in props.noDataSuggestion"
:key="suggestion.title"
class="app-bar-search-suggestion text-sm font-weight-regular cursor-pointer mt-3"
@click="$emit('itemSelected', suggestion)"
>
<VIcon
size="20"
:icon="suggestion.icon"
class="me-3"
/>
<span class="text-sm">{{ suggestion.title }}</span>
</h6>
</div>
</div>
</VCardText>
</slot>
</div>
</PerfectScrollbar>
</VCard>
</VDialog>
</template>
<style lang="scss">
.app-bar-search-suggestions {
.app-bar-search-suggestion {
&:hover {
color: rgb(var(--v-theme-primary));
}
}
}
.app-bar-autocomplete-box {
.v-field__input {
padding-block-end: 0.425rem;
padding-block-start: 0.9375rem;
}
.v-field__field input {
text-align: start !important;
}
}
.app-bar-search-dialog {
.v-list-item-title {
font-size: 0.875rem !important;
}
.app-bar-search-list {
.v-list-item,
.v-list-subheader {
font-size: 0.75rem;
padding-inline: 1.5rem !important;
}
.v-list-item {
.v-list-item__append {
.enter-icon {
visibility: hidden;
}
}
&:hover,
&:active,
&:focus {
.v-list-item__append {
.enter-icon {
visibility: visible;
}
}
}
}
.v-list-subheader {
line-height: 1;
min-block-size: auto;
padding-block: 0.6875rem 0.3125rem;
text-transform: uppercase;
}
}
}
</style>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 16px;
}
</style>

View File

@ -1,152 +0,0 @@
<script setup lang="ts">
interface Props {
collapsed?: boolean
noActions?: boolean
actionCollapsed?: boolean
actionRefresh?: boolean
actionRemove?: boolean
title?: string
}
interface Emit {
(e: 'collapsed', isContentCollapsed: boolean): void
(e: 'refresh', hideOverlay: () => void): void
(e: 'trash'): void
}
const props = withDefaults(defineProps<Props>(), {
collapsed: false,
noActions: false,
actionCollapsed: false,
actionRefresh: false,
actionRemove: false,
title: undefined,
})
const emit = defineEmits<Emit>()
// inherit Attribute make false
defineOptions({
inheritAttrs: false,
})
const isContentCollapsed = ref(props.collapsed)
const isCardRemoved = ref(false)
const isOverlayVisible = ref(false)
// hiding overlay
const hideOverlay = () => {
isOverlayVisible.value = false
}
// trigger collapse
const triggerCollapse = () => {
isContentCollapsed.value = !isContentCollapsed.value
emit('collapsed', isContentCollapsed.value)
}
// trigger refresh
const triggerRefresh = () => {
isOverlayVisible.value = true
emit('refresh', hideOverlay)
}
// trigger removal
const triggeredRemove = () => {
isCardRemoved.value = true
emit('trash')
}
</script>
<template>
<VExpandTransition>
<!-- TODO remove div when transition work with v-card components: https://github.com/vuetifyjs/vuetify/issues/15111 -->
<div v-if="!isCardRemoved">
<VCard v-bind="$attrs">
<VCardItem>
<VCardTitle v-if="props.title || $slots.title">
<!-- 👉 Title slot and prop -->
<slot name="title">
{{ props.title }}
</slot>
</VCardTitle>
<template #append>
<!-- 👉 Before actions slot -->
<div>
<slot name="before-actions" />
<!-- SECTION Actions buttons -->
<!-- 👉 Collapse button -->
<IconBtn
v-if="(!(actionRemove || actionRefresh) || actionCollapsed) && !noActions"
@click="triggerCollapse"
>
<VIcon
size="20"
icon="mdi-chevron-up"
:style="{ transform: isContentCollapsed ? 'rotate(-180deg)' : null }"
style="transition-duration: 0.28s;"
/>
</IconBtn>
<!-- 👉 Overlay button -->
<IconBtn
v-if="(!(actionRemove || actionCollapsed) || actionRefresh) && !noActions"
@click="triggerRefresh"
>
<VIcon
size="20"
icon="mdi-refresh"
/>
</IconBtn>
<!-- 👉 Close button -->
<IconBtn
v-if="(!(actionRefresh || actionCollapsed) || actionRemove) && !noActions"
@click="triggeredRemove"
>
<VIcon
size="20"
icon="mdi-close"
/>
</IconBtn>
</div>
<!-- !SECTION -->
</template>
</VCardItem>
<!-- 👉 card content -->
<VExpandTransition>
<div
v-show="!isContentCollapsed"
class="v-card-content"
>
<slot />
</div>
</VExpandTransition>
<!-- 👉 Overlay -->
<VOverlay
v-model="isOverlayVisible"
contained
persistent
class="align-center justify-center"
>
<VProgressCircular indeterminate />
</VOverlay>
</VCard>
</div>
</VExpandTransition>
</template>
<style lang="scss">
.v-card-item {
+.v-card-content {
.v-card-text:first-child {
padding-block-start: 0;
}
}
}
</style>

View File

@ -1,125 +0,0 @@
<script lang="ts" setup>
import 'prismjs'
import 'prismjs/themes/prism-tomorrow.css'
import type { Ref } from 'vue'
import Prism from 'vue-prism-component'
interface Props {
title: string
code: CodeProp
codeLanguage?: string
noPadding?: boolean
}
interface CodeProp {
ts: string
js: string
}
const props = withDefaults(defineProps<Props>(), {
codeLanguage: 'markup',
noPadding: false,
})
const preferredCodeLanguage = useStorage('preferredCodeLanguage', 'ts') as unknown as Ref<keyof CodeProp>
const isCodeShown = ref(false)
const { copy, copied } = useClipboard({ source: computed(() => props.code[preferredCodeLanguage.value]) })
</script>
<template>
<VCard>
<VCardItem>
<VCardTitle>{{ props.title }}</VCardTitle>
<template #append>
<IconBtn
:color="isCodeShown ? 'primary' : 'default'"
:class="isCodeShown ? '' : 'text-disabled'"
@click="isCodeShown = !isCodeShown"
>
<VIcon
size="20"
icon="mdi-code-tags"
/>
</IconBtn>
</template>
</VCardItem>
<slot v-if="noPadding" />
<VCardText v-else>
<slot />
</VCardText>
<VExpandTransition>
<div v-show="isCodeShown">
<VDivider />
<VCardText class="flex gap-y-3 flex-column">
<div class="flex justify-end">
<VBtnToggle
v-model="preferredCodeLanguage"
mandatory
variant="outlined"
density="compact"
>
<VBtn
size="x-small"
value="ts"
:color="preferredCodeLanguage === 'ts' ? 'primary' : 'default'"
>
<VIcon
size="x-large"
icon="mdi-language-typescript"
:color="preferredCodeLanguage === 'ts' ? 'primary' : 'secondary'"
/>
</VBtn>
<VBtn
size="x-small"
value="js"
:color="preferredCodeLanguage === 'js' ? 'primary' : 'default'"
>
<VIcon
size="x-large"
icon="mdi-language-javascript"
:color="preferredCodeLanguage === 'js' ? 'primary' : 'secondary'"
/>
</VBtn>
</VBtnToggle>
</div>
<div class="position-relative">
<Prism
:key="props.code[preferredCodeLanguage]"
:language="props.codeLanguage"
>
{{ props.code[preferredCodeLanguage] }}
</Prism>
<IconBtn
class="position-absolute app-card-code-copy-icon"
color="white"
@click="() => { copy() }"
>
<VIcon
:icon="copied ? 'mdi-check' : 'mdi-content-copy'"
size="20"
/>
</IconBtn>
</div>
</VCardText>
</div>
</VExpandTransition>
</VCard>
</template>
<style lang="scss">
@use "@styles/variables/_vuetify.scss";
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
border-radius: vuetify.$card-border-radius;
}
.app-card-code-copy-icon {
inset-block-start: 1.2em;
inset-inline-end: 0.8em;
}
</style>

View File

@ -1,417 +0,0 @@
<script setup lang="ts">
import FlatPickr from 'vue-flatpickr-component'
import { useTheme } from 'vuetify'
// @ts-expect-error There won't be declaration file for it
import { filterFieldProps, makeVFieldProps } from 'vuetify/lib/components/VField/VField'
// @ts-expect-error There won't be declaration file for it
import { filterInputProps, makeVInputProps } from 'vuetify/lib/components/VInput/VInput'
// @ts-expect-error There won't be declaration file for it
import { filterInputAttrs } from 'vuetify/lib/util/helpers'
import { useThemeConfig } from '@core/composable/useThemeConfig'
import { ref } from 'vue'
const props = defineProps({
...makeVInputProps({
hideDetails: 'auto',
}),
...makeVFieldProps({
variant: 'outlined',
color: 'primary',
}),
})
const emit = defineEmits<Emit>()
interface Emit {
(e: 'update:modelValue', val: string): void
(e: 'click:clear', el: MouseEvent): void
}
// inherit Attribute make false
defineOptions({
inheritAttrs: false,
})
const attrs = useAttrs()
const [rootAttrs, compAttrs] = filterInputAttrs(attrs)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [{ modelValue: _, ...inputProps }] = filterInputProps(props)
const [fieldProps] = filterFieldProps(props)
const refFlatPicker = ref()
const { focused } = useFocus(refFlatPicker)
const isCalendarOpen = ref(false)
const isInlinePicker = ref(false)
// flat picker prop manipulation
if (compAttrs.config && compAttrs.config.inline) {
isInlinePicker.value = compAttrs.config.inline
Object.assign(compAttrs, { altInputClass: 'inlinePicker' })
}
// v-field clear prop
const onClear = (el: MouseEvent) => {
el.stopPropagation()
nextTick(() => {
emit('update:modelValue', '')
emit('click:clear', el)
})
}
const { theme } = useThemeConfig()
const vuetifyTheme = useTheme()
const vuetifyThemesName = Object.keys(vuetifyTheme.themes.value)
// Themes class added to flat-picker component for light and dark support
const updateThemeClassInCalendar = () => {
// Flatpickr don't render it's instance in mobile and device simulator
if (!refFlatPicker.value.fp.calendarContainer)
return
vuetifyThemesName.forEach(t => {
refFlatPicker.value.fp.calendarContainer.classList.remove(`v-theme--${t}`)
})
refFlatPicker.value.fp.calendarContainer.classList.add(`v-theme--${vuetifyTheme.global.name.value}`)
}
watch(theme, updateThemeClassInCalendar)
onMounted(() => {
updateThemeClassInCalendar()
})
const emitModelValue = (val: string) => {
emit('update:modelValue', val)
}
</script>
<template>
<!-- v-input -->
<VInput
v-bind="{ ...inputProps, ...rootAttrs }"
:model-value="modelValue"
:hide-details="props.hideDetails"
class="position-relative"
>
<template #default="{ isDirty, isValid, isReadonly }">
<!-- v-field -->
<VField
v-bind="fieldProps"
:active="focused || isDirty.value || isCalendarOpen"
:focused="focused || isCalendarOpen"
role="textbox"
:dirty="isDirty.value || props.dirty"
:error="isValid.value === false"
@click:clear="onClear"
>
<template #default="{ props: vFieldProps }">
<div v-bind="vFieldProps">
<!-- flat-picker -->
<FlatPickr
v-if="!isInlinePicker"
v-bind="compAttrs"
ref="refFlatPicker"
:model-value="modelValue"
class="flat-picker-custom-style"
:disabled="isReadonly.value"
@on-open="isCalendarOpen = true"
@on-close="isCalendarOpen = false"
@update:model-value="emitModelValue"
/>
<!-- simple input for inline prop -->
<input
v-if="isInlinePicker"
:value="modelValue"
class="flat-picker-custom-style"
type="text"
>
</div>
</template>
</VField>
</template>
</VInput>
<!-- flat picker for inline props -->
<FlatPickr
v-if="isInlinePicker"
v-bind="compAttrs"
ref="refFlatPicker"
:model-value="modelValue"
@update:model-value="emitModelValue"
@on-open="isCalendarOpen = true"
@on-close="isCalendarOpen = false"
/>
</template>
<style lang="scss">
/* stylelint-disable no-descending-specificity */
@use "flatpickr/dist/flatpickr.css";
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
.flat-picker-custom-style {
position: absolute;
color: inherit;
inline-size: 100%;
inset: 0;
outline: none;
padding-block: 0;
padding-inline: var(--v-field-padding-start);
}
$heading-color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
$body-color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
// hide the input when your picker is inline
input[altinputclass="inlinePicker"] {
display: none;
}
.flatpickr-calendar {
border-radius: 10px;
background-color: rgb(var(--v-theme-surface));
margin-block-start: 0.1875rem;
@include mixins_elevation.elevation(6);
&.hasTime .flatpickr-time {
border-block-start: none;
}
.numInputWrapper:hover {
background: transparent;
}
.flatpickr-rContainer {
.flatpickr-weekdays {
padding-inline: 0.625rem;
}
.flatpickr-days {
.dayContainer {
justify-content: center !important;
padding-block-end: 0.625rem;
padding-block-start: 0;
.flatpickr-day {
block-size: 2.625rem;
line-height: 2.625rem;
margin-block-start: 0 !important;
max-inline-size: 2.625rem;
}
}
}
}
.flatpickr-day {
color: $body-color;
&.today {
border-color: rgb(var(--v-theme-primary));
&:hover {
border-color: rgb(var(--v-theme-primary));
background: transparent;
color: $body-color;
}
}
&.selected,
&.selected:hover {
border-color: rgb(var(--v-theme-primary));
background: rgb(var(--v-theme-primary));
color: rgb(var(--v-theme-on-primary));
}
&.inRange,
&.inRange:hover {
border: none;
background: rgba(var(--v-theme-primary), 0.1) !important;
box-shadow: none !important;
color: rgb(var(--v-theme-primary));
}
&.startRange {
box-shadow: none;
}
&.endRange {
box-shadow: none;
}
&.startRange,
&.endRange,
&.startRange:hover,
&.endRange:hover {
border-color: rgb(var(--v-theme-primary));
background: rgb(var(--v-theme-primary));
color: rgb(var(--v-theme-on-primary));
}
&.selected.startRange + .endRange:not(:nth-child(7n + 1)),
&.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
&.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
box-shadow: -10px 0 0 rgb(var(--v-theme-primary));
}
&.flatpickr-disabled,
&.prevMonthDay:not(.startRange,.inRange),
&.nextMonthDay:not(.endRange,.inRange) {
opacity: var(--v-disabled-opacity);
}
&:hover {
border-color: rgba(var(--v-theme-surface-variant), var(--v-hover-opacity));
background: rgba(var(--v-theme-surface-variant), var(--v-hover-opacity));
}
}
.flatpickr-weekday {
color: $heading-color;
font-size: 14px;
font-weight: 500;
}
&::after,
&::before {
display: none;
}
.flatpickr-months {
padding-block-start: 0.625rem;
.flatpickr-prev-month,
.flatpickr-next-month {
fill: $body-color;
&:hover i,
&:hover svg {
fill: $body-color;
}
}
}
.flatpickr-current-month span.cur-month {
font-weight: 300;
}
&.open {
// Open calendar above overlay
z-index: 2401;
}
&.hasTime.open {
.flatpickr-time {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
block-size: auto;
}
}
}
// Time picker hover & focus bg color
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: transparent;
}
// Time picker
.flatpickr-time {
.flatpickr-am-pm,
.flatpickr-time-separator,
input {
color: $body-color;
}
.numInputWrapper {
span {
&.arrowUp {
&::after {
border-block-end-color: rgb(var(--v-border-color));
}
}
&.arrowDown {
&::after {
border-block-start-color: rgb(var(--v-border-color));
}
}
}
}
}
// Added bg color for flatpickr input only as it has default readonly attribute
.flatpickr-input[readonly],
.flatpickr-input ~ .form-control[readonly],
.flatpickr-human-friendly[readonly] {
background-color: inherit;
opacity: 1 !important;
}
// week sections
.flatpickr-weekdays {
margin-block-start: 8px;
}
// Month and year section
.flatpickr-current-month {
.flatpickr-monthDropdown-months {
appearance: none;
}
.flatpickr-monthDropdown-months,
.numInputWrapper {
padding: 2px;
border-radius: 4px;
color: $heading-color;
font-size: 1rem;
font-weight: 400;
transition: all 0.15s ease-out;
span {
display: none;
}
.cur-year {
font-weight: 400;
}
.flatpickr-monthDropdown-month {
background-color: rgb(var(--v-theme-surface));
}
}
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
color: $body-color;
}
.flatpickr-months {
padding-block: 0.3rem;
padding-inline: 0;
.flatpickr-prev-month,
.flatpickr-next-month {
inset-block-start: 0.625rem !important;
}
.flatpickr-next-month {
inset-inline-end: 0.375rem !important;
}
.flatpickr-prev-month {
inset-inline-start: 0.25rem !important;
}
}
</style>

View File

@ -1,29 +0,0 @@
<script setup lang="ts">
interface Props {
title: string
}
interface Emit {
(e: 'cancel', el: MouseEvent): void
}
const props = defineProps<Props>()
defineEmits<Emit>()
</script>
<template>
<div class="px-5 py-3 flex align-center bg-var-theme-background">
<h3 class="font-weight-medium text-xl">
{{ props.title }}
</h3>
<VSpacer />
<slot name="beforeClose" />
<IconBtn @click="$emit('cancel')">
<VIcon
size="18"
icon="mdi-close"
/>
</IconBtn>
</div>
</template>

View File

@ -1,88 +0,0 @@
<script setup lang="ts">
interface Props {
totalInput?: number
default?: string
}
interface Emit {
(e: 'updateOtp', val: string): void
}
const props = withDefaults(defineProps<Props>(), {
totalInput: 6,
default: '',
})
const emit = defineEmits<Emit>()
const digits = ref<string[]>([])
const refOtpComp = ref<HTMLInputElement | null>(null)
digits.value = props.default.split('')
const defaultStyle = {
style: 'max-width: 54px; text-align: center;',
}
// eslint-disable-next-line sonarjs/cognitive-complexity
const handleKeyDown = (event: KeyboardEvent, index: number) => {
if (event.code !== 'Tab' && event.code !== 'ArrowRight' && event.code !== 'ArrowLeft')
event.preventDefault()
if (event.code === 'Backspace') {
digits.value[index - 1] = ''
if (refOtpComp.value !== null && index > 1) {
const inputEl = refOtpComp.value.children[index - 2].querySelector('input')
if (inputEl)
inputEl.focus()
}
}
const numberRegExp = /^([0-9])$/
if (numberRegExp.test(event.key)) {
digits.value[index - 1] = event.key
if (refOtpComp.value !== null && index !== 0 && index < refOtpComp.value.children.length) {
const inputEl = refOtpComp.value.children[index].querySelector('input')
if (inputEl)
inputEl.focus()
}
}
emit('updateOtp', digits.value.join(''))
}
</script>
<template>
<div>
<h6 class="text-base font-weight-bold mb-3">
Type your 6 digit security code
</h6>
<div
ref="refOtpComp"
class="flex align-center gap-4"
>
<VTextField
v-for="i in props.totalInput"
:key="i"
:model-value="digits[i - 1]"
v-bind="defaultStyle"
maxlength="1"
@keydown="handleKeyDown($event, i)"
/>
</div>
</div>
</template>
<style lang="scss">
.v-field__field {
input {
padding: 0.5rem;
font-size: 1.25rem;
text-align: center;
}
}
</style>

View File

@ -1,230 +0,0 @@
<script setup lang="ts">
import pricingIllustration1 from '@images/misc/pricing-illustration-1.png'
import pricingIllustration2 from '@images/misc/pricing-illustration-2.png'
import pricingIllustration3 from '@images/misc/pricing-illustration-3.png'
interface Pricing {
title?: string
xs?: number | string
sm?: number | string
md?: string | number
lg?: string | number
xl?: string | number
}
const props = defineProps<Pricing>()
const annualMonthlyPlanPriceToggler = ref(true)
const pricingPlans = [
{
name: 'Basic',
tagLine: 'A simple start for everyone',
logo: pricingIllustration1,
monthlyPrice: 0,
yearlyPrice: 0,
isPopular: false,
current: true,
features: [
'100 responses a month',
'Unlimited forms and surveys',
'Unlimited fields',
'Basic form creation tools',
'Up to 2 subdomains',
],
},
{
name: 'Standard',
tagLine: 'For small to medium businesses',
logo: pricingIllustration2,
monthlyPrice: 49,
yearlyPrice: 480,
isPopular: true,
current: false,
features: [
'Unlimited responses',
'Unlimited forms and surveys',
'Instagram profile page',
'Google Docs integration',
'Custom “Thank you” page',
],
},
{
name: 'Enterprise',
tagLine: 'Solution for big organizations',
logo: pricingIllustration3,
monthlyPrice: 99,
yearlyPrice: 960,
isPopular: false,
current: false,
features: [
'PayPal payments',
'Logic Jumps',
'File upload with 5GB storage',
'Custom domain support',
'Stripe integration',
],
},
]
</script>
<template>
<!-- 👉 Title and subtitle -->
<div class="text-center">
<h4 class="pricing-title text-h4 mb-4">
{{ props.title ? props.title : 'Pricing Plans' }}
</h4>
<p class="text-sm mb-0">
All plans include 40+ advanced tools and features to boost your product.
</p>
<p class="text-sm mb-0">
Choose the best plan to fit your needs.
</p>
</div>
<!-- 👉 Annual and monthly price toggler -->
<div class="flex align-center justify-center mx-auto py-10">
<VLabel
for="pricing-plan-toggle"
class="me-2"
>
Monthly
</VLabel>
<div class="position-relative">
<div class="position-absolute price-chip d-none d-sm-flex">
<VIcon
icon="mdi-arrow-down-left"
size="24"
class="text-disabled flip-in-rtl mt-1 me-1"
/>
<VChip
text="Save up to 10%"
size="x-small"
color="primary"
/>
</div>
<VSwitch
id="pricing-plan-toggle"
v-model="annualMonthlyPlanPriceToggler"
label="Annual"
/>
</div>
</div>
<!-- SECTION pricing plans -->
<VRow>
<VCol
v-for="plan in pricingPlans"
:key="plan.logo"
v-bind="props"
cols="12"
>
<!-- 👉 Card -->
<VCard
flat
border
:class="plan.isPopular ? 'border-primary border-opacity-100' : ''"
>
<VCardText
style="height: 4.125rem;"
class="text-end"
>
<!-- 👉 Popular -->
<VChip
v-show="plan.isPopular"
color="primary"
size="small"
class="font-weight-semibold"
>
Popular
</VChip>
</VCardText>
<!-- 👉 Plan logo -->
<VCardText class="text-center">
<VImg
:height="100"
:src="plan.logo"
class="mx-auto mb-5"
/>
<!-- 👉 Plan name -->
<h5 class="text-h5 mb-2">
{{ plan.name }}
</h5>
<p class="mb-0">
{{ plan.tagLine }}
</p>
</VCardText>
<!-- 👉 Plan price -->
<VCardText class="position-relative text-center">
<div class="flex justify-center align-center">
<sup class="text-sm font-weight-medium me-1">$</sup>
<h1 class="text-5xl font-weight-medium text-primary">
{{ annualMonthlyPlanPriceToggler ? Math.floor(Number(plan.yearlyPrice) / 12) : plan.monthlyPrice }}
</h1>
<sub class="text-sm font-weight-medium ms-1 mt-4">/month</sub>
</div>
<!-- 👉 Annual Price -->
<span
v-show="annualMonthlyPlanPriceToggler"
class="position-absolute text-caption font-weight-medium"
style="inset-inline: 0;"
>
{{ plan.yearlyPrice === 0 ? 'free' : `USD ${plan.yearlyPrice}/Year` }}
</span>
</VCardText>
<!-- 👉 Plan features -->
<VCardText class="mt-2">
<VList class="card-list mb-5">
<VListItem
v-for="feature in plan.features"
:key="feature"
>
<template #prepend>
<VIcon
:size="14"
icon="mdi-circle-outline"
class="me-3"
/>
</template>
<VListItemTitle class="text-body-2">
{{ feature }}
</VListItemTitle>
</VListItem>
</VList>
<VBtn
block
:color="plan.current ? 'success' : 'primary'"
:variant="plan.isPopular ? 'elevated' : 'tonal'"
>
{{ plan.yearlyPrice === 0 ? 'Your Current Plan' : 'Upgrade' }}
</VBtn>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- !SECTION -->
</template>
<style lang="scss" scoped>
.price-chip {
display: flex;
inset-block-start: -1.5rem;
inset-inline-end: -6.5rem;
// inset-inline-start: 2rem;
}
.card-list {
--v-card-list-gap: 0.75rem;
}
</style>

View File

@ -1,77 +0,0 @@
<script setup lang="ts">
import AppSearchHeaderBgDark from '@images/pages/app-search-header-bg-dark.png'
import AppSearchHeaderBgLight from '@images/pages/app-search-header-bg-light.png'
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
interface Props {
title?: string
subtitle?: string
customClass?: string
}
const props = defineProps<Props>()
defineOptions({
inheritAttrs: false,
})
const themeBackgroundImg = useGenerateImageVariant(AppSearchHeaderBgLight, AppSearchHeaderBgDark)
</script>
<template>
<!-- 👉 Search Banner -->
<VCard
flat
class="text-center search-header"
:class="props.customClass"
:style="`background: url(${themeBackgroundImg});`"
>
<VCardText>
<h5 class="text-h5 font-weight-semibold text-primary mb-2">
{{ props.title }}
</h5>
<p>{{ props.subtitle }}</p>
<!-- 👉 Search Input -->
<VTextField
v-bind="$attrs"
placeholder="Ask a question.."
class="search-header-input mx-auto my-6"
>
<template #prepend-inner>
<VIcon
icon="mdi-magnify"
size="23"
/>
</template>
</VTextField>
</VCardText>
</VCard>
</template>
<style lang="scss">
.search-header {
padding: 4rem !important;
background-size: cover !important;
}
// search input
.search-header-input {
border-radius: 0.5rem !important;
background-color: rgb(var(--v-theme-surface));
max-inline-size: 32.125rem;
.v-field__prepend-inner {
i {
inset-block-start: 3px !important;
}
}
}
@media (max-width: 37.5rem) {
.search-header {
padding: 1.5rem !important;
}
}
</style>

View File

@ -1,40 +0,0 @@
<script lang="ts" setup>
const vm = getCurrentInstance()
const buyNowUrl = ref(vm?.appContext.config.globalProperties.buyNowUrl || 'https://1.envato.market/materialize_admin')
watch(buyNowUrl, val => {
if (vm)
vm.appContext.config.globalProperties.buyNowUrl = val
})
</script>
<template>
<VBtn
color="error"
class="product-buy-now"
:href="buyNowUrl"
target="_blank"
rel="noopener noreferrer"
>
Buy Now
</VBtn>
</template>
<style lang="scss" scoped>
.product-buy-now {
position: fixed;
// To keep buy now button on top of v-layout. E.g. Email app
z-index: 999;
inset-block-end: 5%;
inset-inline-end: 79px;
.v-application &.v-btn.v-btn--elevated {
box-shadow: 0 1px 20px 1px rgb(var(--v-theme-error)) !important;
&:hover {
box-shadow: none !important;
}
}
}
</style>

View File

@ -1,147 +0,0 @@
<script setup lang="ts">
interface Details {
number: string
name: string
expiry: string
cvv: string
isPrimary: boolean
type: string
}
interface Emit {
(e: 'submit', value: Details): void
(e: 'update:isDialogVisible', value: boolean): void
}
interface Props {
cardDetails?: Details
isDialogVisible: boolean
}
const props = withDefaults(defineProps<Props>(), {
cardDetails: () => ({
number: '',
name: '',
expiry: '',
cvv: '',
isPrimary: false,
type: '',
}),
})
const emit = defineEmits<Emit>()
const cardDetails = ref<Details>(structuredClone(toRaw(props.cardDetails)))
watch(props, () => {
cardDetails.value = structuredClone(toRaw(props.cardDetails))
})
const formSubmit = () => {
emit('submit', cardDetails.value)
}
</script>
<template>
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 650 "
:model-value="props.isDialogVisible"
@update:model-value="val => $emit('update:isDialogVisible', val)"
>
<VCard class="pa-5 pa-sm-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="$emit('update:isDialogVisible', false)"
/>
<!-- 👉 Title -->
<VCardItem class="text-center">
<VCardTitle class="text-2xl mb-3">
{{ props.cardDetails.name ? 'Edit Card' : 'Add New New Card' }}
</VCardTitle>
<VCardSubtitle>
{{ props.cardDetails.name ? 'Edit your saved card details' : 'Add your saved card details' }}
</VCardSubtitle>
</VCardItem>
<VCardText class="mt-6">
<VForm @submit.prevent="() => {}">
<VRow>
<!-- 👉 Card Number -->
<VCol cols="12">
<VTextField
v-model="cardDetails.number"
label="Card Number"
type="number"
/>
</VCol>
<!-- 👉 Card Name -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="cardDetails.name"
label="Name"
/>
</VCol>
<!-- 👉 Card Expiry -->
<VCol
cols="6"
md="3"
>
<VTextField
v-model="cardDetails.expiry"
label="Expiry Date"
/>
</VCol>
<!-- 👉 Card CVV -->
<VCol
cols="6"
md="3"
>
<VTextField
v-model="cardDetails.cvv"
type="password"
label="CVV Code"
/>
</VCol>
<!-- 👉 Card Primary Set -->
<VCol cols="12">
<VSwitch
v-model="cardDetails.isPrimary"
label="Save Card for future billing?"
/>
</VCol>
<!-- 👉 Card actions -->
<VCol
cols="12"
class="text-center"
>
<VBtn
class="me-4"
type="submit"
@click="formSubmit"
>
Submit
</VBtn>
<VBtn
color="secondary"
variant="tonal"
@click="$emit('update:isDialogVisible', false)"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,55 +0,0 @@
<script setup lang="ts">
import { kFormatter } from '@core/utils/formatters'
interface Props {
title: string
color?: string
icon: string
stats: number
change: number
}
const props = withDefaults(defineProps<Props>(), {
color: 'primary',
})
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
</script>
<template>
<VCard>
<VCardText class="flex align-center">
<VAvatar
size="40"
rounded
:color="props.color"
variant="tonal"
class="me-4"
>
<VIcon
:icon="props.icon"
size="24"
/>
</VAvatar>
<div class="flex flex-column">
<div class="flex align-center flex-wrap">
<h6 class="text-h6">
{{ kFormatter(props.stats) }}
</h6>
<div
v-if="props.change"
:class="`${isPositive ? 'text-success' : 'text-error'}`"
>
<VIcon
size="24"
:icon="isPositive ? 'mdi-chevron-up' : 'mdi-chevron-down'"
/>
<span class="text-caption">{{ Math.abs(props.change) }}%</span>
</div>
</div>
<span class="text-caption">{{ props.title }}</span>
</div>
</VCardText>
</VCard>
</template>

View File

@ -1,63 +0,0 @@
<script setup lang="ts">
interface Props {
title: string
color?: string
icon: string
stats: string
change: number
subtitle: string
}
const props = withDefaults(defineProps<Props>(), {
color: 'primary',
})
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
</script>
<template>
<VCard>
<VCardText class="flex align-center">
<VAvatar
v-if="props.icon"
rounded
size="38"
variant="tonal"
:color="props.color"
>
<VIcon
:icon="props.icon"
size="24"
/>
</VAvatar>
<VSpacer />
<div
v-if="props.change"
:class="isPositive ? 'text-success' : 'text-error'"
class="flex align-center text-sm font-weight-medium mt-n4"
>
<span>{{ isPositive ? `+${props.change}` : props.change }}%</span>
<VIcon :icon="isPositive ? 'mdi-chevron-up' : 'mdi-chevron-down'" />
</div>
</VCardText>
<VCardText>
<h6 class="text-h6 me-2 mt-2 mb-1">
{{ props.stats }}
</h6>
<p class="text-sm">
{{ props.title }}
</p>
<VChip
size="x-small"
class="font-weight-medium"
>
<span class="text-truncate">{{ props.subtitle }}</span>
</VChip>
</VCardText>
</VCard>
</template>

View File

@ -1,65 +0,0 @@
<script setup lang="ts">
interface Props {
title: string
subtitle: string
stats: string
change: number
image: string
imgWidth: number
color?: string
}
const props = withDefaults(defineProps<Props>(), {
color: 'primary',
})
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
</script>
<template>
<VCard>
<VCardText>
<h6 class="text-base font-weight-semibold mb-2">
{{ props.title }}
</h6>
<VChip
v-if="props.subtitle"
size="x-small"
:color="props.color"
class="mb-5"
>
{{ props.subtitle }}
</VChip>
<div class="flex align-center flex-wrap">
<h5 class="text-h5 me-2">
{{ props.stats }}
</h5>
<span
class="text-caption"
:class="isPositive ? 'text-success' : 'text-error'"
>
{{ isPositive ? `+${props.change}` : props.change }}%
</span>
</div>
</VCardText>
<VSpacer />
<div class="illustrator-img">
<VImg
v-if="props.image"
:src="props.image"
:width="props.imgWidth"
/>
</div>
</VCard>
</template>
<style lang="scss">
.illustrator-img {
position: absolute;
inset-block-end: 0;
inset-inline-end: 5%;
}
</style>

View File

@ -1,73 +0,0 @@
<script setup lang="ts">
interface Props {
confirmationMsg: string
isDialogVisible: boolean
}
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
(e: 'confirm', value: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const updateModelValue = (val: boolean) => {
emit('update:isDialogVisible', val)
}
const onConfirmation = () => {
emit('confirm', true)
updateModelValue(false)
}
const onCancel = () => {
emit('confirm', false)
emit('update:isDialogVisible', false)
}
</script>
<template>
<!-- 👉 Confirm Dialog -->
<VDialog
max-width="500"
:model-value="props.isDialogVisible"
@update:model-value="updateModelValue"
>
<VCard class="text-center px-10 py-6">
<VCardText>
<VBtn
icon
variant="outlined"
color="warning"
class="mb-4"
style="width: 88px; height: 88px; pointer-events: none;"
>
<span class="text-5xl">!</span>
</VBtn>
<h6 class="text-lg font-weight-medium">
{{ props.confirmationMsg }}
</h6>
</VCardText>
<VCardActions class="align-center justify-center gap-2">
<VBtn
variant="elevated"
@click="onConfirmation"
>
Confirm
</VBtn>
<VBtn
color="secondary"
variant="tonal"
@click="onCancel"
>
Cancel
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>

View File

@ -1,75 +0,0 @@
<script lang="ts" setup>
import type { CustomInputContent, GridColumn } from '@core/types'
interface Props {
selectedCheckbox: string[]
checkboxContent: CustomInputContent[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedCheckbox', value: string[]): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedCheckbox)))
watch(selectedOption, () => {
emit('update:selectedCheckbox', selectedOption.value)
})
</script>
<template>
<VRow
v-if="props.checkboxContent && selectedOption"
v-model="selectedOption"
>
<VCol
v-for="item in props.checkboxContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox rounded cursor-pointer"
:class="selectedOption.includes(item.value) ? 'active' : ''"
>
<div>
<VCheckbox
v-model="selectedOption"
:value="item.value"
/>
</div>
<div class="flex-grow-1">
<div class="flex align-center mb-1">
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<VSpacer />
<span v-if="item.subtitle">{{ item.subtitle }}</span>
</div>
<p class="text-sm mb-0">
{{ item.desc }}
</p>
</div>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox {
display: flex;
align-items: flex-start;
gap: 0.375rem;
.v-checkbox {
margin-block-start: -0.375rem;
}
.cr-title {
font-weight: 500;
}
}
</style>

View File

@ -1,77 +0,0 @@
<script lang="ts" setup>
import type { CustomInputContent, GridColumn } from '@core/types'
interface Props {
selectedCheckbox: string[]
checkboxContent: CustomInputContent[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedCheckbox', value: string[]): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedCheckbox)))
watch(selectedOption, () => {
emit('update:selectedCheckbox', selectedOption.value)
})
</script>
<template>
<VRow
v-if="props.checkboxContent && selectedOption"
v-model="selectedOption"
>
<VCol
v-for="item in props.checkboxContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox rounded cursor-pointer"
:class="selectedOption.includes(item.value) ? 'active' : ''"
>
<div class="flex flex-column align-center text-center gap-2">
<VIcon
size="32"
:icon="item.icon"
class="text-high-emphasis"
/>
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<p class="text-sm mb-0">
{{ item.desc }}
</p>
</div>
<div>
<VCheckbox
v-model="selectedOption"
:value="item.value"
/>
</div>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox {
display: flex;
flex-direction: column;
gap: 0.375rem;
.v-checkbox {
margin-block-end: -0.375rem;
}
.cr-title {
font-weight: 500;
}
}
</style>

View File

@ -1,80 +0,0 @@
<script lang="ts" setup>
import type { GridColumn } from '@core/types'
interface Props {
selectedCheckbox: string[]
checkboxContent: { bgImage: string; value: string }[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedCheckbox', value: string[]): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedCheckbox)))
watch(selectedOption, () => {
emit('update:selectedCheckbox', selectedOption.value)
})
</script>
<template>
<VRow
v-if="props.checkboxContent && selectedOption"
v-model="selectedOption"
>
<VCol
v-for="item in props.checkboxContent"
:key="item.value"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox rounded cursor-pointer"
:class="selectedOption.includes(item.value) ? 'active' : ''"
>
<div>
<VCheckbox
v-model="selectedOption"
:value="item.value"
/>
</div>
<img
:src="item.bgImage"
alt="bg-img"
class="custom-checkbox-image"
>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox {
position: relative;
padding: 0;
border-width: 2px;
transition: all 0.5s;
.custom-checkbox-image {
block-size: 100%;
inline-size: 100%;
}
.v-checkbox {
position: absolute;
inset-block-start: 0;
inset-inline-end: 0;
visibility: hidden;
}
&:hover,
&.active {
.v-checkbox {
visibility: visible;
}
}
}
</style>

View File

@ -1,74 +0,0 @@
<script lang="ts" setup>
import type { CustomInputContent, GridColumn } from '@core/types'
interface Props {
selectedRadio: string
radioContent: CustomInputContent[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedRadio', value: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedRadio)))
watch(selectedOption, () => {
emit('update:selectedRadio', selectedOption.value)
})
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
v-model="selectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio rounded cursor-pointer"
:class="selectedOption === item.value ? 'active' : ''"
>
<div>
<VRadio :value="item.value" />
</div>
<div class="flex-grow-1">
<div class="flex align-center mb-1">
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<VSpacer />
<span v-if="item.subtitle">{{ item.subtitle }}</span>
</div>
<p class="text-sm mb-0">
{{ item.desc }}
</p>
</div>
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio {
display: flex;
align-items: flex-start;
gap: 0.375rem;
.v-radio {
margin-block-start: -0.25rem;
}
.cr-title {
font-weight: 500;
}
}
</style>

View File

@ -1,77 +0,0 @@
<script lang="ts" setup>
import type { CustomInputContent, GridColumn } from '@core/types'
interface Props {
selectedRadio: string
radioContent: CustomInputContent[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedRadio', value: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedRadio)))
watch(selectedOption, () => {
emit('update:selectedRadio', selectedOption.value)
})
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
v-model="selectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio rounded cursor-pointer"
:class="selectedOption === item.value ? 'active' : ''"
>
<div class="flex flex-column align-center text-center gap-2">
<VIcon
size="32"
:icon="item.icon"
class="text-high-emphasis"
/>
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<p class="text-sm mb-0">
{{ item.desc }}
</p>
</div>
<div>
<VRadio :value="item.value" />
</div>
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio {
display: flex;
flex-direction: column;
gap: 0.375rem;
.v-radio {
margin-block-end: -0.25rem;
}
.cr-title {
font-weight: 500;
}
}
</style>

View File

@ -1,65 +0,0 @@
<script lang="ts" setup>
import type { GridColumn } from '@core/types'
interface Props {
selectedRadio: string
radioContent: { bgImage: string; value: string }[]
gridColumn?: GridColumn
}
interface Emit {
(e: 'update:selectedRadio', value: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const selectedOption = ref(structuredClone(toRaw(props.selectedRadio)))
watch(selectedOption, () => {
emit('update:selectedRadio', selectedOption.value)
})
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
v-model="selectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.bgImage"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio rounded cursor-pointer"
:class="selectedOption === item.value ? 'active' : ''"
>
<img
:src="item.bgImage"
alt="bg-img"
class="custom-radio-image"
>
<VRadio :value="item.value" />
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio {
padding: 0;
border-width: 2px;
.custom-radio-image {
block-size: 100%;
inline-size: 100%;
}
.v-radio {
visibility: hidden;
}
}
</style>

View File

@ -1,22 +0,0 @@
<script setup lang="ts">
interface Props {
title: string
divider?: boolean
}
const props = withDefaults(defineProps<Props>(), {
divider: true,
})
</script>
<template>
<VDivider v-if="props.divider" />
<div class="customizer-section">
<p class="text-caption">
{{ props.title }}
</p>
<slot />
</div>
</template>

View File

@ -1,20 +0,0 @@
<script setup lang="ts">
interface Props {
icon?: string
iconSize?: string
}
const props = withDefaults(defineProps<Props>(), {
icon: 'mdi-close',
iconSize: '22',
})
</script>
<template>
<IconBtn class="v-dialog-close-btn">
<VIcon
:icon="props.icon"
:size="props.iconSize"
/>
</IconBtn>
</template>

View File

@ -1,79 +0,0 @@
<script setup lang="ts">
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
(e: 'submit', value: string): void
}
interface Props {
mobileNumber?: string
isDialogVisible: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const phoneNumber = ref(structuredClone(toRaw(props.mobileNumber)))
const formSubmit = () => {
if (phoneNumber.value) {
emit('submit', phoneNumber.value)
emit('update:isDialogVisible', false)
}
}
const resetPhoneNumber = () => {
phoneNumber.value = structuredClone(toRaw(props.mobileNumber))
emit('update:isDialogVisible', false)
}
</script>
<template>
<VDialog
max-width="600"
:model-value="props.isDialogVisible"
@update:model-value="(val) => $emit('update:isDialogVisible', val)"
>
<VCard
title="Verify Your Mobile Number for SMS"
subtitle="Enter your mobile phone number with country code and we will send you a verification code."
class="pa-5 pa-sm-8"
>
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="resetPhoneNumber"
/>
<VCardText>
<VForm @submit.prevent="() => {}">
<VTextField
v-model="phoneNumber"
name="mobile"
label="Phone Number"
class="mb-4"
/>
<div class="flex flex-wrap justify-end gap-3">
<VBtn
color="secondary"
variant="tonal"
@click="resetPhoneNumber"
>
Cancel
</VBtn>
<VBtn
type="submit"
@click="formSubmit"
>
continue
<VIcon
end
icon="mdi-chevron-right"
class="flip-in-rtl"
/>
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,22 +0,0 @@
<script setup lang="ts">
interface Props {
errorCode: string
errorTitle: string
errorDescription: string
}
const props = defineProps<Props>()
</script>
<template>
<div class="text-center mb-4">
<!-- 👉 Title and subtitle -->
<h1 class="text-h1">
{{ props.errorCode }}
</h1>
<h5 class="text-h5 font-weight-semibold mb-3">
{{ props.errorTitle }}
</h5>
<p>{{ props.errorDescription }}</p>
</div>
</template>

View File

@ -1,56 +0,0 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { defineEmits, ref, watch } from 'vue'
import type { Anchor } from 'vuetify/lib/components';
import type { I18nLanguage } from '@layouts/types';
const props = withDefaults(defineProps<Props>(), {
location: 'bottom end',
});
const emit = defineEmits<{
(e: 'change', id: string): void;
}>();
interface Props {
languages: I18nLanguage[];
location?: Anchor;
}
let locale = ref(useI18n({ useScope: 'global' }).locale)
watch(locale, (val: string) => {
document.documentElement.setAttribute('lang', val as string);
});
let currentLang = ref(localStorage.getItem('lang') || 'en');
function changeLang(lang: string){
locale.value = lang
currentLang.value = lang
emit('change', lang)
}
</script>
<template>
<div
class="dropdown"
:class="currentLang === 'ar'?'dropdown-right': 'dropdown-bottom dropdown-end'"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon icon="mdi-translate" class="text-2xl" />
</label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
>
<li v-for="lang in props.languages" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
:class="{ 'text-primary': currentLang === lang.i18nLang }"
@click="changeLang(lang.i18nLang)"
>{{ lang.label }}</a
>
</li>
</ul>
</div>
</template>

View File

@ -1,27 +0,0 @@
<script lang="ts" setup>
import type { ItemProps } from 'vuetify/composables/item'
interface Props {
menuList?: ItemProps[]
itemProps?: boolean
}
const props = defineProps<Props>()
</script>
<template>
<IconBtn>
<VIcon icon="mdi-dots-vertical" />
<VMenu
v-if="props.menuList"
activator="parent"
>
<VList
density="compact"
:items="props.menuList"
:item-props="props.itemProps"
/>
</VMenu>
</IconBtn>
</template>

View File

@ -1,196 +0,0 @@
<script lang="ts" setup>
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import type { Anchor } from 'vuetify/lib/components'
import { avatarText } from '@core/utils/formatters'
import type { Notification } from '@layouts/types'
interface Props {
notifications: Notification[]
badgeProps?: unknown
location?: Anchor
}
interface Emit {
(e: 'read', value: number[]): void
(e: 'unread', value: number[]): void
(e: 'remove', value: number): void
(e: 'click:notification', value: Notification): void
}
const props = withDefaults(defineProps<Props>(), {
location: 'bottom end',
badgeProps: undefined,
})
const emit = defineEmits<Emit>()
const isAllMarkRead = computed(() => {
return props.notifications.some(item => item.isRead === true)
})
const markAllReadOrUnread = () => {
const allNotificationsIds = props.notifications.map(item => item.id)
if (isAllMarkRead.value)
emit('unread', allNotificationsIds)
else
emit('read', allNotificationsIds)
}
</script>
<template>
<VBadge
:model-value="!!props.badgeProps"
v-bind="props.badgeProps"
>
<IconBtn>
<VBadge
dot
:model-value="!!props.notifications.length"
color="error"
bordered
offset-x="1"
offset-y="1"
>
<VIcon icon="mdi-bell-outline" />
</VBadge>
<VMenu
activator="parent"
width="380px"
:location="props.location"
offset="14px"
:close-on-content-click="false"
>
<VCard class="flex flex-column">
<!-- 👉 Header -->
<VCardItem class="notification-section">
<VCardTitle class="text-lg">
Notifications
</VCardTitle>
<template #append>
<IconBtn
v-show="props.notifications.length"
@click="markAllReadOrUnread"
>
<VIcon :icon="isAllMarkRead ? 'mdi-email-open-outline' : 'mdi-email-outline' " />
<VTooltip
activator="parent"
location="start"
>
{{ isAllMarkRead ? 'Mark all as read' : 'Mark all as unread' }}
</VTooltip>
</IconBtn>
</template>
</VCardItem>
<VDivider />
<!-- 👉 Notifications list -->
<PerfectScrollbar :options="{ wheelPropagation: false }">
<VList class="py-0">
<template
v-for="notification in props.notifications"
:key="notification.title"
>
<VListItem
link
lines="one"
min-height="66px"
class="list-item-hover-class"
@click="$emit('click:notification', notification)"
>
<!-- Slot: Prepend -->
<!-- Handles Avatar: Image, Icon, Text -->
<template #prepend>
<VListItemAction start>
<VAvatar
:color="notification.color || 'primary'"
:image="notification.img || undefined"
:icon="notification.icon || undefined"
size="40"
variant="tonal"
>
<span v-if="notification.text">{{ avatarText(notification.text) }}</span>
</VAvatar>
</VListItemAction>
</template>
<VListItemTitle>{{ notification.title }}</VListItemTitle>
<VListItemSubtitle>{{ notification.subtitle }}</VListItemSubtitle>
<span class="text-xs text-disabled">{{ notification.time }}</span>
<!-- Slot: Append -->
<template #append>
<div class="flex flex-column align-center gap-4">
<VBadge
dot
:color="notification.isRead ? 'primary' : '#a8aaae'"
:class="`${!notification.isRead ? 'visible-in-hover' : ''} ms-1`"
@click.stop="$emit(notification.isRead ? 'unread' : 'read', [notification.id])"
/>
<div style=" width: 28px;height: 28px;">
<IconBtn
size="x-small"
class="visible-in-hover"
@click="$emit('remove', notification.id)"
>
<VIcon
size="20"
icon="mdi-close"
/>
</IconBtn>
</div>
</div>
</template>
</VListItem>
<VDivider />
</template>
<VListItem
v-show="!props.notifications.length"
class="text-center text-medium-emphasis"
>
<VListItemTitle>No Notification Found!</VListItemTitle>
</VListItem>
</VList>
</PerfectScrollbar>
<!-- 👉 Footer -->
<VCardActions
v-show="props.notifications.length"
class="notification-footer"
>
<VBtn block>
VIEW ALL NOTIFICATIONS
</VBtn>
</VCardActions>
</VCard>
</VMenu>
</IconBtn>
</VBadge>
</template>
<style lang="scss">
.notification-section {
padding: 14px !important;
}
.notification-footer {
padding: 6px !important;
}
.list-item-hover-class {
.visible-in-hover {
display: none;
}
&:hover {
.visible-in-hover {
display: block;
}
}
}
</style>

View File

@ -1,55 +0,0 @@
<script setup lang="ts">
interface Props {
isDialogVisible: boolean
}
interface Emit {
(e: 'update:isDialogVisible', val: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const dialogVisibleUpdate = (val: boolean) => {
emit('update:isDialogVisible', val)
}
</script>
<template>
<VDialog
:model-value="props.isDialogVisible"
class="v-dialog-xl"
@update:model-value="dialogVisibleUpdate"
>
<VCard class="pricing-dialog pa-5 pa-sm-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="emit('update:isDialogVisible', false)"
/>
<VCardText>
<AppPricing
title="Subscription Plan"
lg="4"
/>
</VCardText>
<VCardText class="text-center">
<p class="text-sm">
Still Not Convinced? Start with a 14-day FREE trial!
</p>
<VBtn>Start Your Trial</VBtn>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss">
.pricing-dialog {
.pricing-title {
font-size: 1.5rem;
}
}
</style>

View File

@ -1,185 +0,0 @@
<script setup lang="ts">
interface Props {
isDialogVisible: boolean
}
interface Emit {
(e: 'update:isDialogVisible', val: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const dialogVisibleUpdate = (val: boolean) => {
emit('update:isDialogVisible', val)
}
const referAndEarnSteps = [
{
icon: 'mdi-message-outline',
title: 'Send Invitation 👍🏻',
subtitle: 'Send your referral link to your friend',
},
{
icon: 'mdi-clipboard-outline',
title: 'Registration 😎',
subtitle: 'Let them register to our services',
},
{
icon: 'mdi-medal-outline',
title: 'Free Trial 🎉',
subtitle: 'Your friend will get 30 days free trial',
},
]
</script>
<template>
<VDialog
:model-value="props.isDialogVisible"
max-width="900"
@update:model-value="dialogVisibleUpdate"
>
<VCard class="refer-and-earn-dialog">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="emit('update:isDialogVisible', false)"
/>
<VCardText class="pa-5 pa-sm-10">
<h5 class="text-h5 text-center mb-3">
Refer & Earn
</h5>
<p class="text-sm-body-1 text-center">
Invite your friend to vuexy, if they sign up, you and your friend will get 30 days free trial
</p>
<VRow class="text-center mt-6">
<VCol
v-for="step in referAndEarnSteps"
:key="step.title"
cols="12"
sm="4"
>
<VAvatar
variant="tonal"
size="100"
color="primary"
>
<VIcon
size="40"
:icon="step.icon"
/>
</VAvatar>
<h6 class="text-base mt-2 mb-3">
{{ step.title }}
</h6>
<span class="text-sm">{{ step.subtitle }}</span>
</VCol>
</VRow>
</VCardText>
<VDivider />
<VCardText class="pa-5 pa-sm-10">
<h6 class="text-h6 mb-4">
Invite your friends
</h6>
<p class="mb-3 text-sm">
Enter your friend's email address and invite them to join Materio 😍
</p>
<VForm
class="flex align-center gap-4"
@submit.prevent="() => {}"
>
<VTextField
density="compact"
placeholder="johnDoe@gmail.com"
/>
<VBtn type="submit">
Submit
</VBtn>
</VForm>
<h6 class="text-h6 mb-4 mt-8">
Share the referral link
</h6>
<p class="mb-2 text-sm">
You can also copy and send it or share it on your social media. 🚀
</p>
<VForm
class="flex align-center flex-wrap gap-3"
@submit.prevent="() => {}"
>
<VTextField
density="compact"
placeholder="http://referral.link"
class="refer-link-input me-1"
>
<template #append-inner>
<VBtn variant="text">
COPY LINK
</VBtn>
</template>
</VTextField>
<div class="flex gap-3">
<VBtn
icon
class="rounded"
color="#3B5998"
size="40"
>
<VIcon
color="white"
icon="mdi-facebook"
/>
</VBtn>
<VBtn
icon
class="rounded"
color="#55ACEE"
size="40"
>
<VIcon
color="white"
icon="mdi-twitter"
/>
</VBtn>
<VBtn
icon
class="rounded"
color="#007BB6"
size="40"
>
<VIcon
color="white"
icon="mdi-linkedin"
/>
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss">
.refer-link-input {
.v-field--appended {
padding-inline-end: 0;
}
.v-field__append-inner {
padding-block-start: 0.125rem;
}
}
</style>

View File

@ -1,203 +0,0 @@
<script setup lang="ts">
import avatar1 from '@images/avatars/avatar-1.png'
import avatar2 from '@images/avatars/avatar-2.png'
import avatar3 from '@images/avatars/avatar-3.png'
import avatar4 from '@images/avatars/avatar-4.png'
import avatar5 from '@images/avatars/avatar-5.png'
import avatar6 from '@images/avatars/avatar-6.png'
import avatar7 from '@images/avatars/avatar-7.png'
import avatar8 from '@images/avatars/avatar-8.png'
interface Props {
isDialogVisible: boolean
}
interface Emit {
(e: 'update:isDialogVisible', val: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const dialogVisibleUpdate = (val: boolean) => {
emit('update:isDialogVisible', val)
}
type Permission = 'Owner' | 'Can Edit' | 'Can Comment' | 'Can View'
interface Member {
avatar: string
name: string
email: string
permission: Permission
}
const membersList: Member[] = [
{
avatar: avatar1,
name: 'Lester Palmer',
email: 'jerrod98@gmail.com',
permission: 'Can Edit',
},
{
avatar: avatar2,
name: 'Mattie Blair',
email: 'prudence.boehm@yahoo.com',
permission: 'Owner',
},
{
avatar: avatar3,
name: 'Marvin Wheeler',
email: 'rumet@jujpejah.net',
permission: 'Can Comment',
},
{
avatar: avatar4,
name: 'Nannie Ford',
email: 'negza@nuv.io',
permission: 'Can View',
},
{
avatar: avatar5,
name: 'Julian Murphy',
email: 'lunebame@umdomgu.net',
permission: 'Can Edit',
},
{
avatar: avatar6,
name: 'Sophie Gilbert',
email: 'ha@sugit.gov',
permission: 'Can View',
},
{
avatar: avatar7,
name: 'Chris Watkins',
email: 'zokap@mak.org',
permission: 'Can Comment',
},
{
avatar: avatar8,
name: 'Adelaide Nichols',
email: 'ujinomu@jigo.com',
permission: 'Can Edit',
},
]
</script>
<template>
<VDialog
:model-value="props.isDialogVisible"
max-width="900"
@update:model-value="dialogVisibleUpdate"
>
<VCard class="share-project-dialog pa-5 pa-sm-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
size="small"
@click="emit('update:isDialogVisible', false)"
/>
<VCardText>
<h5 class="text-h5 text-center mb-3">
Share Project
</h5>
<p class="text-sm text-center">
Share project with a team members
</p>
<p class="text-xl font-weight-medium mb-2">
Add Members
</p>
<VAutocomplete
:items="membersList"
item-title="name"
item-value="name"
placeholder="Add project members..."
density="compact"
>
<template #item="{ props: listItemProp, item }">
<VListItem v-bind="listItemProp">
<template #prepend>
<VAvatar
:image="item.raw.avatar"
size="30"
/>
</template>
</VListItem>
</template>
</VAutocomplete>
<h6 class="text-h6 mb-4 mt-8">
8 Members
</h6>
<VList class="card-list">
<VListItem
v-for="member in membersList"
:key="member.name"
>
<template #prepend>
<VAvatar :image="member.avatar" />
</template>
<VListItemTitle class="text-sm">
{{ member.name }}
</VListItemTitle>
<VListItemSubtitle>
{{ member.email }}
</VListItemSubtitle>
<template #append>
<VBtn
variant="text"
color="default"
:icon="$vuetify.display.xs"
>
<span class="d-none d-sm-block">{{ member.permission }}</span>
<VIcon icon="mdi-chevron-down" />
<VMenu activator="parent">
<VList :selected="[member.permission]">
<VListItem
v-for="(item, index) in ['Owner', 'Can Edit', 'Can Comment', 'Can View']"
:key="index"
:value="item"
>
<VListItemTitle>{{ item }}</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</VBtn>
</template>
</VListItem>
</VList>
<div class="flex justify-space-between flex-wrap gap-3 mt-6">
<h6 class="text-sm font-weight-semibold flex align-start">
<VIcon
icon="mdi-account-group-outline"
class="me-2"
/>
<span>Public to Master - Pixinvent</span>
</h6>
<VBtn
variant="text"
prepend-icon="mdi-link"
>
Copy Project Link
</VBtn>
</div>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss">
.share-project-dialog {
.card-list {
--v-card-list-gap: 1rem;
}
}
</style>

View File

@ -1,76 +0,0 @@
<script setup lang="ts">
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
interface Shortcut {
icon: string
title: string
subtitle: string
to: object | string
}
interface Props {
togglerIcon?: string
shortcuts: Shortcut[]
}
const props = withDefaults(defineProps<Props>(), {
togglerIcon: 'mdi-view-grid-plus-outline',
})
const router = useRouter()
</script>
<template>
<IconBtn>
<VIcon :icon="props.togglerIcon" />
<VMenu
activator="parent"
offset="14px"
location="bottom end"
>
<VCard
width="340"
max-height="560"
class="flex flex-column"
>
<VCardItem class="py-4">
<VCardTitle>Shortcuts</VCardTitle>
<template #append>
<IconBtn>
<VIcon icon="mdi-view-grid-plus-outline" />
</IconBtn>
</template>
</VCardItem>
<VDivider />
<PerfectScrollbar :options="{ wheelPropagation: false }">
<VRow class="ma-0 mt-n1">
<VCol
v-for="(shortcut, index) in props.shortcuts"
:key="shortcut.title"
cols="6"
class="text-center border-t cursor-pointer pa-4"
:class="(index + 1) % 2 ? 'border-e' : ''"
@click="router.push(shortcut.to)"
>
<VAvatar
variant="tonal"
size="48"
>
<VIcon :icon="shortcut.icon" />
</VAvatar>
<h6 class="text-base font-weight-semibold mt-2 mb-0">
{{ shortcut.title }}
</h6>
<span class="text-sm">{{ shortcut.subtitle }}</span>
</VCol>
</VRow>
</PerfectScrollbar>
</VCard>
</VMenu>
</IconBtn>
</template>

View File

@ -1,376 +0,0 @@
<script setup lang="tsx">
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { useTheme } from 'vuetify'
import { staticPrimaryColor } from '@/plugins/vuetify/theme'
import { useThemeConfig } from '@core/composable/useThemeConfig'
import { RouteTransitions, Skins } from '@core/enums'
import { AppContentLayoutNav, ContentWidth, FooterType, NavbarType } from '@layouts/enums'
import { themeConfig } from '@themeConfig'
// import { useTheme } from 'vuetify'
const isNavDrawerOpen = ref(false)
const {
theme,
skin,
appRouteTransition,
navbarType,
footerType,
isVerticalNavCollapsed,
isVerticalNavSemiDark,
appContentWidth,
appContentLayoutNav,
isAppRtl,
isNavbarBlurEnabled,
isLessThanOverlayNavBreakpoint,
} = useThemeConfig()
// 👉 Primary Color
const vuetifyTheme = useTheme()
// const vuetifyThemesName = Object.keys(vuetifyTheme.themes.value)
const initialThemeColors = JSON.parse(JSON.stringify(vuetifyTheme.current.value.colors))
const colors = ['primary', 'secondary', 'success', 'info', 'warning', 'error']
// It will set primary color for current theme only
const setPrimaryColor = (color: string) => {
const currentThemeName = vuetifyTheme.name.value
vuetifyTheme.themes.value[currentThemeName].colors.primary = color
// We need to store this color value in localStorage so vuetify plugin can pick on next reload
localStorage.setItem(`${themeConfig.app.title}-${currentThemeName}ThemePrimaryColor`, color)
// Update initial loader color
localStorage.setItem(`${themeConfig.app.title}-initial-loader-color`, color)
}
/*
This will return static color for first indexed color
If we don't make first (primary) color as static then when another color is selected then we will have two theme colors with same hex codes and it will show two check marks
*/
const getBoxColor = (color: string, index: number) => index ? color : staticPrimaryColor
const { width: windowWidth } = useWindowSize()
const headerValues = computed(() => {
const entries = Object.entries(NavbarType)
if (appContentLayoutNav.value === AppContentLayoutNav.Horizontal)
return entries.filter(([_, val]) => val !== NavbarType.Hidden)
return entries
})
</script>
<template>
<template v-if="!isLessThanOverlayNavBreakpoint(windowWidth)">
<VBtn
icon
size="small"
class="app-customizer-toggler rounded-s rounded-0"
style="z-index: 1001;"
@click="isNavDrawerOpen = true"
>
<VIcon icon="mdi-cog" />
</VBtn>
<VNavigationDrawer
v-model="isNavDrawerOpen"
temporary
location="end"
width="400"
:scrim="false"
class="app-customizer"
>
<!-- 👉 Header -->
<div class="customizer-heading flex align-center justify-space-between">
<div>
<h6 class="text-h6">
THEME CUSTOMIZER
</h6>
<span class="text-body-1">Customize & Preview in Real Time</span>
</div>
<IconBtn @click="isNavDrawerOpen = false">
<VIcon
icon="mdi-close"
size="20"
/>
</IconBtn>
</div>
<VDivider />
<PerfectScrollbar
tag="ul"
:options="{ wheelPropagation: false }"
>
<!-- SECTION Theming -->
<CustomizerSection
title="THEMING"
:divider="false"
>
<!-- 👉 Skin -->
<h6 class="text-base font-weight-regular">
Skins
</h6>
<VRadioGroup
v-model="skin"
inline
>
<VRadio
v-for="[key, val] in Object.entries(Skins)"
:key="key"
:label="key"
:value="val"
/>
</VRadioGroup>
<!-- 👉 Theme -->
<h6 class="mt-3 text-base font-weight-regular">
Theme
</h6>
<VRadioGroup
v-model="theme"
inline
>
<VRadio
v-for="themeOption in ['system', 'light', 'dark']"
:key="themeOption"
:label="themeOption"
:value="themeOption"
class="text-capitalize"
/>
</VRadioGroup>
<!-- 👉 Primary color -->
<h6 class="mt-3 text-base font-weight-regular">
Primary Color
</h6>
<div class="flex gap-x-4 mt-2">
<div
v-for="(color, index) in colors"
:key="color"
style="width: 2.5rem; height: 2.5rem; border-radius: 0.5rem; transition: all 0.25s ease;"
:style="{ backgroundColor: getBoxColor(initialThemeColors[color], index) }"
class="cursor-pointer flex align-center justify-center"
:class="{ 'elevation-4': vuetifyTheme.current.value.colors.primary === getBoxColor(initialThemeColors[color], index) }"
@click="setPrimaryColor(getBoxColor(initialThemeColors[color], index))"
>
<VFadeTransition>
<VIcon
v-show="vuetifyTheme.current.value.colors.primary === (getBoxColor(initialThemeColors[color], index))"
icon="mdi-check"
color="white"
/>
</VFadeTransition>
</div>
</div>
</CustomizerSection>
<!-- !SECTION -->
<!-- SECTION LAYOUT -->
<CustomizerSection title="LAYOUT">
<!-- 👉 Content Width -->
<h6 class="text-base font-weight-regular">
Content width
</h6>
<VRadioGroup
v-model="appContentWidth"
inline
>
<VRadio
v-for="[key, val] in Object.entries(ContentWidth)"
:key="key"
:label="key"
:value="val"
/>
</VRadioGroup>
<!-- 👉 Navbar Type -->
<h6 class="mt-3 text-base font-weight-regular">
{{ appContentLayoutNav === AppContentLayoutNav.Vertical ? 'Navbar' : 'Header' }} Type
</h6>
<VRadioGroup
v-model="navbarType"
inline
>
<VRadio
v-for="[key, val] in headerValues"
:key="key"
:label="key"
:value="val"
/>
</VRadioGroup>
<!-- 👉 Footer Type -->
<h6 class="mt-3 text-base font-weight-regular">
Footer Type
</h6>
<VRadioGroup
v-model="footerType"
inline
>
<VRadio
v-for="[key, val] in Object.entries(FooterType)"
:key="key"
:label="key"
:value="val"
/>
</VRadioGroup>
<!-- 👉 Navbar blur -->
<div class="flex align-center justify-space-between">
<VLabel
for="customizer-navbar-blur"
class="text-high-emphasis"
>
Navbar Blur
</VLabel>
<div>
<VSwitch
id="customizer-navbar-blur"
v-model="isNavbarBlurEnabled"
class="ms-2"
/>
</div>
</div>
</CustomizerSection>
<!-- !SECTION -->
<!-- SECTION Menu -->
<CustomizerSection title="MENU">
<!-- 👉 Menu Type -->
<h6 class="text-base font-weight-regular">
Menu Type
</h6>
<VRadioGroup
v-model="appContentLayoutNav"
inline
>
<VRadio
v-for="[key, val] in Object.entries(AppContentLayoutNav)"
:key="key"
:label="key"
:value="val"
/>
</VRadioGroup>
<!-- 👉 Collapsed Menu -->
<div
v-if="appContentLayoutNav === AppContentLayoutNav.Vertical"
class="flex align-center justify-space-between"
>
<VLabel
for="customizer-menu-collapsed"
class="text-high-emphasis"
>
Collapsed Menu
</VLabel>
<div>
<VSwitch
id="customizer-menu-collapsed"
v-model="isVerticalNavCollapsed"
class="ms-2"
/>
</div>
</div>
<!-- 👉 Semi Dark Menu -->
<div
class="align-center justify-space-between"
:class="vuetifyTheme.global.name.value === 'light' && appContentLayoutNav === AppContentLayoutNav.Vertical ? 'flex' : 'd-none'"
>
<VLabel
for="customizer-menu-semi-dark"
class="text-high-emphasis"
>
Semi Dark Menu
</VLabel>
<div>
<VSwitch
id="customizer-menu-semi-dark"
v-model="isVerticalNavSemiDark"
class="ms-2"
/>
</div>
</div>
</CustomizerSection>
<!-- !SECTION -->
<!-- SECTION MISC -->
<CustomizerSection title="MISC">
<!-- 👉 RTL -->
<div class="flex align-center justify-space-between">
<VLabel
for="customizer-rtl"
class="text-high-emphasis"
>
RTL
</VLabel>
<div>
<VSwitch
id="customizer-rtl"
v-model="isAppRtl"
class="ms-2"
/>
</div>
</div>
<!-- 👉 Route Transition -->
<div class="mt-6">
<VRow>
<VCol
cols="5"
class="flex align-center"
>
<VLabel
for="route-transition"
class="text-high-emphasis"
>
Router Transition
</VLabel>
</VCol>
<VCol cols="7">
<VSelect
id="route-transition"
v-model="appRouteTransition"
:items="Object.entries(RouteTransitions).map(([key, value]) => ({ key, value }))"
item-title="key"
item-value="value"
single-line
/>
</VCol>
</VRow>
</div>
</CustomizerSection>
<!-- !SECTION -->
</PerfectScrollbar>
</VNavigationDrawer>
</template>
</template>
<style lang="scss">
.app-customizer {
.customizer-section {
padding: 1.25rem;
}
.customizer-heading {
padding-block: 0.875rem;
padding-inline: 1.25rem;
}
.v-navigation-drawer__content {
display: flex;
flex-direction: column;
}
}
.app-customizer-toggler {
position: fixed !important;
inset-block-start: 50%;
inset-inline-end: 0;
transform: translateY(-50%);
}
</style>

View File

@ -1,57 +0,0 @@
<script setup lang="ts">
import { useThemeConfig } from '@core/composable/useThemeConfig';
import type { ThemeSwitcherTheme } from '@layouts/types';
import { onMounted, watch } from 'vue';
const props = defineProps<{
themes: ThemeSwitcherTheme[];
}>();
const { theme } = useThemeConfig();
const {
state: currentThemeName,
next: getNextThemeName,
index: currentThemeIndex,
} = useCycleList(
props.themes.map(t => t.name),
{ initialValue: theme.value }
);
const changeTheme = () => {
theme.value = getNextThemeName();
};
const changeMode = (val: 'dark' | 'light' | 'system') => {
let value = val;
if (theme.value === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
value = 'dark';
}
if (value === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
}
};
// Update icon if theme is changed from other sources
watch(theme, (val: 'dark' | 'light' | 'system') => {
currentThemeName.value = val;
changeMode(val);
});
onMounted(() => {
if (currentThemeName.value) {
changeMode(currentThemeName.value);
}
});
</script>
<template>
<IconBtn @click="changeTheme">
<VIcon :icon="props.themes[currentThemeIndex].icon" />
<VTooltip activator="parent" open-delay="1000">
<span class="text-capitalize">{{ currentThemeName }}</span>
</VTooltip>
</IconBtn>
</template>

View File

@ -1,158 +0,0 @@
<script setup lang="ts">
import AddAuthenticatorAppDialog from '@core/components/AddAuthenticatorAppDialog.vue'
import EnableOneTimePasswordDialog from '@core/components/EnableOneTimePasswordDialog.vue'
interface Emit {
(e: 'update:isDialogVisible', value: boolean): void
}
interface Props {
isDialogVisible: boolean
smsCode?: string
authAppCode?: string
}
const props = withDefaults(defineProps<Props>(), {
isDialogVisible: false,
smsCode: '',
authAppCode: '',
})
const emit = defineEmits<Emit>()
const authMethods = [
{
icon: 'mdi-cog-outline',
title: 'Authenticator Apps',
subtitle: 'Get code from an app like Google Authenticator or Microsoft Authenticator.',
method: 'authApp',
},
{
icon: 'mdi-message-outline',
title: 'SMS',
subtitle: 'We will send a code via SMS if you need to use your backup login method.',
method: 'sms',
},
]
const selectedMethod = ref(['authApp'])
const isAuthAppDialogVisible = ref(false)
const isSmsDialogVisible = ref(false)
const openSelectedMethodDialog = () => {
if (selectedMethod.value[0] === 'authApp') {
isAuthAppDialogVisible.value = true
isSmsDialogVisible.value = false
emit('update:isDialogVisible', false)
}
if (selectedMethod.value[0] === 'sms') {
isAuthAppDialogVisible.value = false
isSmsDialogVisible.value = true
emit('update:isDialogVisible', false)
}
}
</script>
<template>
<VDialog
max-width="900"
:model-value="props.isDialogVisible"
@update:model-value="(val) => $emit('update:isDialogVisible', val)"
>
<VCard class="pa-5 pa-sm-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="$emit('update:isDialogVisible', false)"
/>
<VCardItem class="text-center">
<VCardTitle class="text-h5 mb-3">
Select Authentication Method
</VCardTitle>
<VCardSubtitle>
You also need to select a method by which the proxy authenticates to the directory serve.
</VCardSubtitle>
</VCardItem>
<VCardText>
<VList
v-model:selected="selectedMethod"
mandatory
class="card-list auth-method-card"
:class="$vuetify.display.xs ? 'responsive-card' : ''"
>
<VListItem
v-for="item of authMethods"
:key="item.title"
rounded
border
:value="item.method"
class="py-5 px-6 my-6"
:class="selectedMethod[0] === item.method ? 'bg-light-primary border-primary' : 'bg-light-secondary border-secondary'"
style="/* stylelint-disable-next-line max-empty-lines */
--v-border-opacity: 1;"
>
<template #prepend>
<VIcon
:icon="item.icon"
size="38"
/>
</template>
<VListItemTitle class="text-xl font-weight-medium">
{{ item.title }}
</VListItemTitle>
<p class="text-base mb-0">
{{ item.subtitle }}
</p>
</VListItem>
</VList>
<div class="text-end">
<VBtn @click="openSelectedMethodDialog">
continue
<VIcon
end
icon="mdi-chevron-right"
class="flip-in-rtl"
/>
</VBtn>
</div>
</VCardText>
</VCard>
</VDialog>
<AddAuthenticatorAppDialog
v-model:isDialogVisible="isAuthAppDialogVisible"
:auth-code="props.authAppCode"
/>
<EnableOneTimePasswordDialog
v-model:isDialogVisible="isSmsDialogVisible"
:mobile-number="props.smsCode"
/>
</template>
<style lang="scss">
.auth-method-card {
&.card-list .v-list-item {
padding-block: 20px !important;
padding-inline: 30px !important;
}
&.responsive-card {
.v-list-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
text-align: center;
.v-list-item__prepend {
svg {
margin: 0;
}
}
}
}
}
</style>

View File

@ -1,226 +0,0 @@
<script setup lang="ts">
interface UserData {
id: number | null
fullName: string
company: string
role: string
username: string
country: string
contact: string
email: string
currentPlan: string
status: string
avatar: string
taskDone: number | null
projectDone: number | null
taxId: string
language: string
}
interface Props {
userData?: UserData
isDialogVisible: boolean
}
interface Emit {
(e: 'submit', value: UserData): void
(e: 'update:isDialogVisible', val: boolean): void
}
const props = withDefaults(defineProps<Props>(), {
userData: () => ({
id: 0,
fullName: '',
company: '',
role: '',
username: '',
country: '',
contact: '',
email: '',
currentPlan: '',
status: '',
avatar: '',
taskDone: null,
projectDone: null,
taxId: '',
language: '',
}),
})
const emit = defineEmits<Emit>()
const userData = ref<UserData>(structuredClone(toRaw(props.userData)))
const isUseAsBillingAddress = ref(false)
watch(props, () => {
userData.value = structuredClone(toRaw(props.userData))
})
const onFormSubmit = () => {
emit('update:isDialogVisible', false)
emit('submit', userData.value)
}
const onFormReset = () => {
userData.value = structuredClone(toRaw(props.userData))
emit('update:isDialogVisible', false)
}
const dialogVisibleUpdate = (val: boolean) => {
emit('update:isDialogVisible', val)
}
</script>
<template>
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 800"
:model-value="props.isDialogVisible"
@update:model-value="dialogVisibleUpdate"
>
<VCard class="pa-sm-9 pa-5">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="onFormReset"
/>
<VCardItem class="text-center">
<VCardTitle class="text-h5 mb-2">
Edit User Information
</VCardTitle>
<VCardSubtitle>
Updating user details will receive a privacy audit.
</VCardSubtitle>
</VCardItem>
<VCardText>
<!-- 👉 Form -->
<VForm
class="mt-6"
@submit.prevent="onFormSubmit"
>
<VRow>
<!-- 👉 Full Name -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.fullName"
label="Full Name"
/>
</VCol>
<!-- 👉 Username -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.username"
label="Username"
/>
</VCol>
<!-- 👉 Billing Email -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.email"
label="Billing Email"
/>
</VCol>
<!-- 👉 Status -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.status"
label="Status"
/>
</VCol>
<!-- 👉 Tax Id -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.taxId"
label="Tax Id"
/>
</VCol>
<!-- 👉 Contact -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="userData.contact"
label="Contact"
/>
</VCol>
<!-- 👉 Language -->
<VCol
cols="12"
md="6"
>
<VSelect
v-model="userData.language"
label="Language"
:items="['English', 'Spanish', 'Portuguese', 'Russian', 'French', 'German']"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
>
<VSelect
v-model="userData.country"
label="Country"
:items="['USA', 'UK', 'Spain', 'Russia', 'France', 'Germany']"
/>
</VCol>
<!-- 👉 Switch -->
<VCol cols="12">
<VSwitch
v-model="isUseAsBillingAddress"
density="compact"
label="Use as a billing address?"
/>
</VCol>
<!-- 👉 Submit and Cancel -->
<VCol
cols="12"
class="flex flex-wrap justify-center gap-4"
>
<VBtn type="submit">
Submit
</VBtn>
<VBtn
color="secondary"
variant="tonal"
@click="onFormReset"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,89 +0,0 @@
<script setup lang="ts">
interface Emit {
(e: 'update:isDialogVisible', val: boolean): void
}
interface Prop {
isDialogVisible: boolean
}
const props = defineProps<Prop>()
defineEmits<Emit>()
const selectedPlan = ref('standard')
const plansList = [
{ text: 'Basic - $0/month', value: 'basic' },
{ text: 'Standard - $99/month', value: 'standard' },
{ text: 'Enterprise - $499/month', value: 'enterprise' },
{ text: 'Company - $999/month', value: 'company' },
]
</script>
<template>
<!-- 👉 upgrade plan -->
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 650"
:model-value="props.isDialogVisible"
@update:model-value="val => $emit('update:isDialogVisible', val)"
>
<VCard class="py-8">
<!-- 👉 dialog close btn -->
<DialogCloseBtn
variant="text"
size="small"
@click="$emit('update:isDialogVisible', false)"
/>
<VCardItem class="text-center">
<VCardTitle class="text-h5 mb-5">
Upgrade Plan
</VCardTitle>
<VCardSubtitle>
Choose the best plan for user.
</VCardSubtitle>
</VCardItem>
<VCardText class="flex align-center flex-wrap flex-sm-nowrap px-15">
<VSelect
v-model="selectedPlan"
label="Choose Plan"
:items="plansList"
item-title="text"
item-value="value"
density="compact"
class="me-3"
/>
<VBtn class="mt-3 mt-sm-0">
Upgrade
</VBtn>
</VCardText>
<VDivider class="my-3" />
<VCardText class="px-15">
<p class="font-weight-medium mb-2">
User current plan is standard plan
</p>
<div class="flex justify-space-between flex-wrap">
<div class="flex align-center me-3">
<sup class="text-primary">$</sup>
<h3 class="text-h3 font-weight-semibold text-primary">
99
</h3>
<sub class="text-body-1 mt-3">/ month</sub>
</div>
<VBtn
color="error"
variant="tonal"
class="mt-3"
>
Cancel Subscription
</VBtn>
</div>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@ -1,26 +0,0 @@
import { useTheme } from 'vuetify'
import { useThemeConfig } from '@core/composable/useThemeConfig'
const { skin } = useThemeConfig()
// composable function to return the image variant as per the current theme and skin
export const useGenerateImageVariant = (imgLight: string, imgDark: string, imgLightBordered?: string, imgDarkBordered?: string, bordered = false) => {
const { global } = useTheme()
return computed(() => {
if (global.name.value === 'light') {
if (skin.value === 'bordered' && bordered)
return imgLightBordered
else
return imgLight
}
if (global.name.value === 'dark') {
if (skin.value === 'bordered' && bordered)
return imgDarkBordered
else
return imgDark
}
})
}

View File

@ -1,29 +0,0 @@
import type { Ref } from 'vue'
import { useDisplay } from 'vuetify'
export const useResponsiveLeftSidebar = (mobileBreakpoint: Ref<boolean> | undefined = undefined) => {
const { mdAndDown, name: currentBreakpoint } = useDisplay()
const _mobileBreakpoint = mobileBreakpoint || mdAndDown
const isLeftSidebarOpen = ref(true)
const setInitialValue = () => {
isLeftSidebarOpen.value = !_mobileBreakpoint.value
}
// Set the initial value of sidebar
setInitialValue()
watch(
currentBreakpoint,
() => {
// Reset left sidebar
isLeftSidebarOpen.value = !_mobileBreakpoint.value
},
)
return {
isLeftSidebarOpen,
}
}

View File

@ -1,36 +0,0 @@
import { VThemeProvider } from 'vuetify/components'
import { AppContentLayoutNav } from '@layouts/enums'
// TODO: Use `VThemeProvider` from dist instead of lib (Using this component from dist causes navbar to loose sticky positioning)
import { useThemeConfig } from '@core/composable/useThemeConfig'
export const useSkins = () => {
const { isVerticalNavSemiDark, skin, appContentLayoutNav } = useThemeConfig()
const layoutAttrs = computed(() => ({
verticalNavAttrs: {
wrapper: h(VThemeProvider, { tag: 'aside' }),
wrapperProps: {
withBackground: true,
theme: isVerticalNavSemiDark.value && appContentLayoutNav.value === AppContentLayoutNav.Vertical
? 'dark'
: undefined,
},
},
}))
const injectSkinClasses = () => {
const bodyClasses = document.body.classList
const genSkinClass = (_skin?: string) => `skin--${_skin}`
watch(skin, (val, oldVal) => {
bodyClasses.remove(genSkinClass(oldVal))
bodyClasses.add(genSkinClass(val))
}, { immediate: true })
}
return {
injectSkinClasses,
layoutAttrs,
}
}

View File

@ -1,154 +0,0 @@
import { useTheme } from 'vuetify'
import { useLayouts } from '@layouts'
import { themeConfig } from '@themeConfig'
export const isDarkPreferred = usePreferredDark()
export const useThemeConfig = () => {
const theme = computed({
get() {
return themeConfig.app.theme.value
},
set(value: typeof themeConfig.app.theme.value) {
themeConfig.app.theme.value = value
localStorage.setItem(`${themeConfig.app.title}-theme`, value.toString())
// We will not reset semi dark value when turning off dark mode because some user think it as bug
// if (value !== 'light')
// // eslint-disable-next-line @typescript-eslint/no-use-before-define
// isVerticalNavSemiDark.value = false
},
})
const isVerticalNavSemiDark = computed({
get() {
return themeConfig.verticalNav.isVerticalNavSemiDark.value
},
set(value: typeof themeConfig.verticalNav.isVerticalNavSemiDark.value) {
themeConfig.verticalNav.isVerticalNavSemiDark.value = value
localStorage.setItem(`${themeConfig.app.title}-isVerticalNavSemiDark`, value.toString())
},
})
const syncVuetifyThemeWithTheme = () => {
const vuetifyTheme = useTheme()
watch([theme, isDarkPreferred], ([val, _]) => {
vuetifyTheme.global.name.value = val === 'system'
? isDarkPreferred.value
? 'dark'
: 'light'
: val
})
}
/*
Set current theme's surface color in localStorage
Why? Because when initial loader is shown (before vue is ready) we need to what's the current theme's surface color.
We will use color stored in localStorage to set the initial loader's background color.
With this we will be able to show correct background color for the initial loader even before vue identify the current theme.
*/
const syncInitialLoaderTheme = () => {
const vuetifyTheme = useTheme()
watch(theme, val => {
// We are not using theme.current.colors.surface because watcher is independent and when this watcher is ran `theme` computed is not updated
localStorage.setItem(`${themeConfig.app.title}-initial-loader-bg`, vuetifyTheme.current.value.colors.surface)
localStorage.setItem(`${themeConfig.app.title}-initial-loader-color`, vuetifyTheme.current.value.colors.primary)
}, {
immediate: true,
})
}
const skin = computed({
get() {
return themeConfig.app.skin.value
},
set(value: typeof themeConfig.app.skin.value) {
themeConfig.app.skin.value = value
localStorage.setItem(`${themeConfig.app.title}-skin`, value)
},
})
const appRouteTransition = computed({
get() {
return themeConfig.app.routeTransition.value
},
set(value: typeof themeConfig.app.routeTransition.value) {
themeConfig.app.routeTransition.value = value
localStorage.setItem(`${themeConfig.app.title}-transition`, value)
},
})
// `@layouts` exports
const {
navbarType,
isNavbarBlurEnabled,
footerType,
isVerticalNavCollapsed,
appContentWidth,
appContentLayoutNav,
horizontalNavType,
isLessThanOverlayNavBreakpoint,
isAppRtl,
switchToVerticalNavOnLtOverlayNavBreakpoint,
} = useLayouts()
// const syncRtlWithRtlLang = (rtlLangs: string[], rtlDefaultLocale: string, ltrDefaultLocale: string) => {
// const { locale } = useI18n({ useScope: 'global' })
// watch(isAppRtl, val => {
// if (val)
// locale.value = rtlDefaultLocale
// else locale.value = ltrDefaultLocale
// })
// watch(locale, val => {
// if (rtlLangs.includes(val))
// isAppRtl.value = true
// else isAppRtl.value = false
// })
// watch(
// [isAppRtl, locale],
// ([valIsAppRTL, valLocale], [oldValIsAppRtl, oldValLocale]) => {
// const isRtlUpdated = valIsAppRTL !== oldValIsAppRtl
// if (isRtlUpdated) {
// if (valIsAppRTL)
// locale.value = rtlDefaultLocale
// else locale.value = ltrDefaultLocale
// }
// else {
// if (rtlLangs.includes(valLocale))
// isAppRtl.value = true
// else isAppRtl.value = false
// }
// },
// )
// }
return {
theme,
isVerticalNavSemiDark,
syncVuetifyThemeWithTheme,
syncInitialLoaderTheme,
skin,
appRouteTransition,
// @layouts exports
navbarType,
isNavbarBlurEnabled,
footerType,
isVerticalNavCollapsed,
appContentWidth,
appContentLayoutNav,
horizontalNavType,
isLessThanOverlayNavBreakpoint,
isAppRtl,
switchToVerticalNavOnLtOverlayNavBreakpoint,
// syncRtlWithRtlLang,
}
}

View File

@ -1,14 +0,0 @@
export const Skins = {
Default: 'default',
Bordered: 'bordered',
} as const
export const RouteTransitions = {
// 'Zoom Fade': 'app-transition-zoom-fade',
// 'Fade Bottom': 'app-transition-fade-bottom',
// 'Slide Fade': 'app-transition-slide-fade',
// 'Zoom out': 'app-transition-zoom-out',
Fade: 'app-transition-fade',
None: 'none',
} as const

View File

@ -1,98 +0,0 @@
import type { ThemeConfig, UserThemeConfig } from './types'
import { RouteTransitions, Skins } from '@core/enums'
import type { UserConfig as LayoutConfig } from '@layouts/types'
import { ref } from 'vue';
export const defineThemeConfig = (
userConfig: UserThemeConfig,
): { themeConfig: ThemeConfig; layoutConfig: LayoutConfig } => {
const localStorageTheme = localStorage.getItem(`${userConfig.app.title}-theme`)
const localStorageIsVerticalNavSemiDark = localStorage.getItem(`${userConfig.app.title}-isVerticalNavSemiDark`)
const localStorageSkin = (() => {
const storageValue = localStorage.getItem(`${userConfig.app.title}-skin`)
return Object.values(Skins).find(v => v === storageValue)
})()
const localStorageTransition = (() => {
const storageValue = localStorage.getItem(`${userConfig.app.title}-transition`)
return Object.values(RouteTransitions).find(v => v === storageValue)
})()
return {
themeConfig: {
app: {
title: userConfig.app.title,
logo: userConfig.app.logo,
contentWidth: ref(userConfig.app.contentWidth),
contentLayoutNav: ref(userConfig.app.contentLayoutNav),
overlayNavFromBreakpoint: userConfig.app.overlayNavFromBreakpoint,
enableI18n: userConfig.app.enableI18n,
theme: ref(localStorageTheme || userConfig.app.theme),
isRtl: ref(userConfig.app.isRtl),
skin: ref(localStorageSkin || userConfig.app.skin),
routeTransition: ref(localStorageTransition || userConfig.app.routeTransition),
iconRenderer: userConfig.app.iconRenderer,
},
navbar: {
type: ref(userConfig.navbar.type),
navbarBlur: ref(userConfig.navbar.navbarBlur),
},
footer: { type: ref(userConfig.footer.type) },
verticalNav: {
isVerticalNavCollapsed: ref(userConfig.verticalNav.isVerticalNavCollapsed),
defaultNavItemIconProps: userConfig.verticalNav.defaultNavItemIconProps,
isVerticalNavSemiDark: ref(localStorageIsVerticalNavSemiDark ? JSON.parse(localStorageIsVerticalNavSemiDark) : userConfig.verticalNav.isVerticalNavSemiDark),
},
horizontalNav: {
type: ref(userConfig.horizontalNav.type),
transition: userConfig.horizontalNav.transition,
},
icons: {
chevronDown: userConfig.icons.chevronDown,
chevronRight: userConfig.icons.chevronRight,
close: userConfig.icons.close,
verticalNavPinned: userConfig.icons.verticalNavPinned,
verticalNavUnPinned: userConfig.icons.verticalNavUnPinned,
sectionTitlePlaceholder: userConfig.icons.sectionTitlePlaceholder,
},
},
layoutConfig: {
app: {
title: userConfig.app.title,
logo: userConfig.app.logo,
contentWidth: userConfig.app.contentWidth,
contentLayoutNav: userConfig.app.contentLayoutNav,
overlayNavFromBreakpoint: userConfig.app.overlayNavFromBreakpoint,
enableI18n: userConfig.app.enableI18n,
isRtl: userConfig.app.isRtl,
iconRenderer: userConfig.app.iconRenderer,
},
navbar: {
type: userConfig.navbar.type,
navbarBlur: userConfig.navbar.navbarBlur,
},
footer: {
type: userConfig.footer.type,
},
verticalNav: {
isVerticalNavCollapsed: userConfig.verticalNav.isVerticalNavCollapsed,
defaultNavItemIconProps: userConfig.verticalNav.defaultNavItemIconProps,
},
horizontalNav: {
type: userConfig.horizontalNav.type,
transition: userConfig.horizontalNav.transition,
},
icons: {
chevronDown: userConfig.icons.chevronDown,
chevronRight: userConfig.icons.chevronRight,
close: userConfig.icons.close,
verticalNavPinned: userConfig.icons.verticalNavPinned,
verticalNavUnPinned: userConfig.icons.verticalNavUnPinned,
sectionTitlePlaceholder: userConfig.icons.sectionTitlePlaceholder,
},
},
}
}

View File

@ -1,703 +0,0 @@
import type { ThemeInstance } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
// 👉 Colors variables
const colorVariables = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const themeSecondaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['medium-emphasis-opacity']})`
const themeDisabledTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['disabled-opacity']})`
const themeBorderColor = `rgba(${hexToRgb(String(themeColors.variables['border-color']))},${themeColors.variables['border-opacity']})`
const themePrimaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['high-emphasis-opacity']})`
return { themeSecondaryTextColor, themeDisabledTextColor, themeBorderColor, themePrimaryTextColor }
}
export const getScatterChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const scatterColors = {
series1: '#ff9f43',
series2: '#7367f0',
series3: '#28c76f',
}
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
zoom: {
type: 'xy',
enabled: true,
},
},
legend: {
position: 'top',
horizontalAlign: 'left',
markers: { offsetX: -3 },
labels: { colors: themeSecondaryTextColor },
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
colors: [scatterColors.series1, scatterColors.series2, scatterColors.series3],
grid: {
borderColor: themeBorderColor,
xaxis: {
lines: { show: true },
},
},
yaxis: {
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
tickAmount: 10,
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
formatter: (val: string) => parseFloat(val).toFixed(1),
},
},
}
}
export const getLineChartSimpleConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
zoom: { enabled: false },
toolbar: { show: false },
},
colors: ['#ff9f43'],
stroke: { curve: 'straight' },
dataLabels: { enabled: false },
markers: {
strokeWidth: 7,
strokeOpacity: 1,
colors: ['#ff9f43'],
strokeColors: ['#fff'],
},
grid: {
padding: { top: -10 },
borderColor: themeBorderColor,
xaxis: {
lines: { show: true },
},
},
tooltip: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
custom(data: any) {
return `<div class='bar-chart pa-2'>
<span>${data.series[data.seriesIndex][data.dataPointIndex]}%</span>
</div>`
},
},
yaxis: {
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
},
categories: [
'7/12',
'8/12',
'9/12',
'10/12',
'11/12',
'12/12',
'13/12',
'14/12',
'15/12',
'16/12',
'17/12',
'18/12',
'19/12',
'20/12',
'21/12',
],
},
}
}
export const getBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
},
colors: ['#00cfe8'],
dataLabels: { enabled: false },
plotOptions: {
bar: {
borderRadius: 8,
barHeight: '30%',
horizontal: true,
startingShape: 'rounded',
},
},
grid: {
borderColor: themeBorderColor,
xaxis: {
lines: { show: false },
},
padding: {
top: -10,
},
},
yaxis: {
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
categories: ['MON, 11', 'THU, 14', 'FRI, 15', 'MON, 18', 'WED, 20', 'FRI, 21', 'MON, 23'],
labels: {
style: { colors: themeDisabledTextColor },
},
},
}
}
export const getCandlestickChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const candlestickColors = {
series1: '#28c76f',
series2: '#ea5455',
}
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
},
plotOptions: {
bar: { columnWidth: '40%' },
candlestick: {
colors: {
upward: candlestickColors.series1,
downward: candlestickColors.series2,
},
},
},
grid: {
padding: { top: -10 },
borderColor: themeBorderColor,
xaxis: {
lines: { show: true },
},
},
yaxis: {
tooltip: { enabled: true },
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
type: 'datetime',
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
},
},
}
}
export const getRadialBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const radialBarColors = {
series1: '#fdd835',
series2: '#32baff',
series3: '#00d4bd',
series4: '#7367f0',
series5: '#FFA1A1',
}
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors)
return {
stroke: { lineCap: 'round' },
labels: ['Comments', 'Replies', 'Shares'],
legend: {
show: true,
position: 'bottom',
labels: {
colors: themeSecondaryTextColor,
},
markers: {
offsetX: -3,
},
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
colors: [radialBarColors.series1, radialBarColors.series2, radialBarColors.series4],
plotOptions: {
radialBar: {
hollow: { size: '30%' },
track: {
margin: 15,
background: themeColors.colors['grey-100'],
},
dataLabels: {
name: {
fontSize: '2rem',
},
value: {
fontSize: '1rem',
color: themeSecondaryTextColor,
},
total: {
show: true,
fontWeight: 400,
label: 'Comments',
fontSize: '1.125rem',
color: themePrimaryTextColor,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter(w: { globals: { seriesTotals: any[]; series: string | any[] } }) {
const totalValue
= w.globals.seriesTotals.reduce((a: number, b: number) => {
return a + b
}, 0) / w.globals.series.length
if (totalValue % 1 === 0)
return `${totalValue}%`
else
return `${totalValue.toFixed(2)}%`
},
},
},
},
},
grid: {
padding: {
top: -35,
bottom: -30,
},
},
}
}
export const getDonutChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const donutColors = {
series1: '#fdd835',
series2: '#00d4bd',
series3: '#826bf8',
series4: '#32baff',
series5: '#ffa1a1',
}
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors)
return {
stroke: { width: 0 },
labels: ['Operational', 'Networking', 'Hiring', 'R&D'],
colors: [donutColors.series1, donutColors.series5, donutColors.series3, donutColors.series2],
dataLabels: {
enabled: true,
formatter: (val: string) => `${parseInt(val, 10)}%`,
},
legend: {
position: 'bottom',
markers: { offsetX: -3 },
labels: { colors: themeSecondaryTextColor },
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
plotOptions: {
pie: {
donut: {
labels: {
show: true,
name: {
fontSize: '1.5rem',
},
value: {
fontSize: '1.5rem',
color: themeSecondaryTextColor,
formatter: (val: string) => `${parseInt(val, 10)}`,
},
total: {
show: true,
fontSize: '1.5rem',
label: 'Operational',
formatter: () => '31%',
color: themePrimaryTextColor,
},
},
},
},
},
responsive: [
{
breakpoint: 992,
options: {
chart: {
height: 380,
},
legend: {
position: 'bottom',
},
},
},
{
breakpoint: 576,
options: {
chart: {
height: 320,
},
plotOptions: {
pie: {
donut: {
labels: {
show: true,
name: {
fontSize: '1rem',
},
value: {
fontSize: '1rem',
},
total: {
fontSize: '1rem',
},
},
},
},
},
},
},
],
}
}
export const getAreaChartSplineConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const areaColors = {
series3: '#e0cffe',
series2: '#b992fe',
series1: '#ab7efd',
}
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
},
tooltip: { shared: false },
dataLabels: { enabled: false },
stroke: {
show: false,
curve: 'straight',
},
legend: {
position: 'top',
horizontalAlign: 'left',
labels: { colors: themeSecondaryTextColor },
markers: {
offsetY: 1,
offsetX: -3,
},
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
colors: [areaColors.series3, areaColors.series2, areaColors.series1],
fill: {
opacity: 1,
type: 'solid',
},
grid: {
show: true,
borderColor: themeBorderColor,
xaxis: {
lines: { show: true },
},
},
yaxis: {
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
},
categories: [
'7/12',
'8/12',
'9/12',
'10/12',
'11/12',
'12/12',
'13/12',
'14/12',
'15/12',
'16/12',
'17/12',
'18/12',
'19/12',
],
},
}
}
export const getColumnChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const columnColors = {
series1: '#826af9',
series2: '#d2b0ff',
bg: '#f8d3ff',
}
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
offsetX: -10,
stacked: true,
parentHeightOffset: 0,
toolbar: { show: false },
},
fill: { opacity: 1 },
dataLabels: { enabled: false },
colors: [columnColors.series1, columnColors.series2],
legend: {
position: 'top',
horizontalAlign: 'left',
labels: { colors: themeSecondaryTextColor },
markers: {
offsetY: 1,
offsetX: -3,
},
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
stroke: {
show: true,
colors: ['transparent'],
},
plotOptions: {
bar: {
columnWidth: '15%',
colors: {
backgroundBarRadius: 10,
backgroundBarColors: [columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg],
},
},
},
grid: {
borderColor: themeBorderColor,
xaxis: {
lines: { show: true },
},
},
yaxis: {
labels: {
style: { colors: themeDisabledTextColor },
},
},
xaxis: {
axisBorder: { show: false },
axisTicks: { color: themeBorderColor },
categories: ['7/12', '8/12', '9/12', '10/12', '11/12', '12/12', '13/12', '14/12', '15/12'],
crosshairs: {
stroke: { color: themeBorderColor },
},
labels: {
style: { colors: themeDisabledTextColor },
},
},
responsive: [
{
breakpoint: 600,
options: {
plotOptions: {
bar: {
columnWidth: '35%',
},
},
},
},
],
}
}
export const getHeatMapChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { themeSecondaryTextColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
},
dataLabels: { enabled: false },
stroke: {
colors: [themeColors.colors.surface],
},
legend: {
position: 'bottom',
labels: {
colors: themeSecondaryTextColor,
},
markers: {
offsetY: 0,
offsetX: -3,
},
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
plotOptions: {
heatmap: {
enableShades: false,
colorScale: {
ranges: [
{ to: 10, from: 0, name: '0-10', color: '#b9b3f8' },
{ to: 20, from: 11, name: '10-20', color: '#aba4f6' },
{ to: 30, from: 21, name: '20-30', color: '#9d95f5' },
{ to: 40, from: 31, name: '30-40', color: '#8f85f3' },
{ to: 50, from: 41, name: '40-50', color: '#8176f2' },
{ to: 60, from: 51, name: '50-60', color: '#7367f0' },
],
},
},
},
grid: {
padding: { top: -20 },
},
yaxis: {
labels: {
style: {
colors: themeDisabledTextColor,
},
},
},
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false },
},
}
}
export const getRadarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const radarColors = {
series1: '#9b88fa',
series2: '#ffa1a1',
}
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
dropShadow: {
top: 1,
blur: 8,
left: 1,
opacity: 0.2,
enabled: false,
},
},
markers: { size: 0 },
fill: { opacity: [1, 0.8] },
colors: [radarColors.series1, radarColors.series2],
stroke: {
width: 0,
show: false,
},
legend: {
labels: {
colors: themeSecondaryTextColor,
},
markers: {
offsetX: -3,
},
itemMargin: {
vertical: 3,
horizontal: 10,
},
},
plotOptions: {
radar: {
polygons: {
strokeColors: themeBorderColor,
connectorColors: themeBorderColor,
},
},
},
grid: {
show: false,
padding: {
top: -20,
bottom: -20,
},
},
yaxis: { show: false },
xaxis: {
categories: ['Battery', 'Brand', 'Camera', 'Memory', 'Storage', 'Display', 'OS', 'Price'],
labels: {
style: {
colors: [
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
themeDisabledTextColor,
],
},
},
},
}
}

View File

@ -1,373 +0,0 @@
import type { ThemeInstance } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
// 👉 Colors variables
const colorVariables = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const themeSecondaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['medium-emphasis-opacity']})`
const themeDisabledTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['disabled-opacity']})`
const themeBorderColor = `rgba(${hexToRgb(String(themeColors.variables['border-color']))},${themeColors.variables['border-opacity']})`
return { labelColor: themeDisabledTextColor, borderColor: themeBorderColor, legendColor: themeSecondaryTextColor }
}
// SECTION config
// 👉 Latest Bar Chart Config
export const getLatestBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
scales: {
x: {
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
ticks: { color: labelColor },
},
y: {
min: 0,
max: 400,
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
ticks: {
stepSize: 100,
color: labelColor,
},
},
},
plugins: {
legend: { display: false },
},
}
}
// 👉 Horizontal Bar Chart Config
export const getHorizontalBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
return {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
elements: {
bar: {
borderRadius: {
topRight: 15,
bottomRight: 15,
},
},
},
layout: {
padding: { top: -4 },
},
scales: {
x: {
min: 0,
grid: {
drawTicks: false,
drawBorder: false,
color: borderColor,
},
ticks: { color: labelColor },
},
y: {
grid: {
borderColor,
display: false,
drawBorder: false,
},
ticks: { color: labelColor },
},
},
plugins: {
legend: {
align: 'end',
position: 'top',
labels: { color: legendColor },
},
},
}
}
// 👉 Line Chart Config
export const getLineChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: { color: labelColor },
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
},
y: {
min: 0,
max: 400,
ticks: {
stepSize: 100,
color: labelColor,
},
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
},
},
plugins: {
legend: {
align: 'end',
position: 'top',
labels: {
padding: 25,
boxWidth: 10,
color: legendColor,
usePointStyle: true,
},
},
},
}
}
// 👉 Radar Chart Config
export const getRadarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
layout: {
padding: { top: -20 },
},
scales: {
r: {
ticks: {
display: false,
maxTicksLimit: 1,
color: labelColor,
},
grid: { color: borderColor },
pointLabels: { color: labelColor },
angleLines: { color: borderColor },
},
},
plugins: {
legend: {
position: 'top',
labels: {
padding: 25,
color: legendColor,
},
},
},
}
}
// 👉 Polar Chart Config
export const getPolarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { legendColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
layout: {
padding: {
top: -5,
bottom: -45,
},
},
scales: {
r: {
grid: { display: false },
ticks: { display: false },
},
},
plugins: {
legend: {
position: 'right',
labels: {
padding: 25,
boxWidth: 9,
color: legendColor,
usePointStyle: true,
},
},
},
}
}
// 👉 Bubble Chart Config
export const getBubbleChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
min: 0,
max: 140,
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
ticks: {
stepSize: 10,
color: labelColor,
},
},
y: {
min: 0,
max: 400,
grid: {
borderColor,
drawBorder: false,
color: borderColor,
},
ticks: {
stepSize: 100,
color: labelColor,
},
},
},
plugins: {
legend: { display: false },
},
}
}
// 👉 Doughnut Chart Config
export const getDoughnutChartConfig = () => {
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
cutout: 80,
plugins: {
legend: {
display: false,
},
},
}
}
// 👉 Scatter Chart Config
export const getScatterChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 800 },
layout: {
padding: { top: -20 },
},
scales: {
x: {
min: 0,
max: 140,
grid: {
borderColor,
drawTicks: false,
drawBorder: false,
color: borderColor,
},
ticks: {
stepSize: 10,
color: labelColor,
},
},
y: {
min: 0,
max: 400,
grid: {
borderColor,
drawTicks: false,
drawBorder: false,
color: borderColor,
},
ticks: {
stepSize: 100,
color: labelColor,
},
},
},
plugins: {
legend: {
align: 'start',
position: 'top',
labels: {
padding: 25,
boxWidth: 9,
color: legendColor,
usePointStyle: true,
},
},
},
}
}
// 👉 Line Area Chart Config
export const getLineAreaChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => {
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
return {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: { top: -20 },
},
scales: {
x: {
grid: {
borderColor,
color: 'transparent',
},
ticks: { color: labelColor },
},
y: {
min: 0,
max: 400,
grid: {
borderColor,
color: 'transparent',
},
ticks: {
stepSize: 100,
color: labelColor,
},
},
},
plugins: {
legend: {
align: 'start',
position: 'top',
labels: {
padding: 25,
boxWidth: 9,
color: legendColor,
usePointStyle: true,
},
},
},
}
}
// !SECTION

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Bar } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
export default defineComponent({
name: 'BarChart',
props: {
chartId: {
type: String,
default: 'bar-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'bar'>[]>,
default: () => ([]),
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Bar), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { Chart as ChartJS, Legend, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Bubble } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
export default defineComponent({
name: 'BubbleChart',
props: {
chartId: {
type: String,
default: 'bubble-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'bubble'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Bubble), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { ArcElement, CategoryScale, Chart as ChartJS, Legend, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Doughnut } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
export default defineComponent({
name: 'DoughnutChart',
props: {
chartId: {
type: String,
default: 'doughnut-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'doughnut'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Doughnut), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { CategoryScale, Chart as ChartJS, Legend, LineElement, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Line } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale)
export default defineComponent({
name: 'LineChart',
props: {
chartId: {
type: String,
default: 'line-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'line'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Line), {
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
options: props.chartOptions,
data: props.chartData,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { ArcElement, Chart as ChartJS, Legend, RadialLinearScale, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { PolarArea } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, ArcElement, RadialLinearScale)
export default defineComponent({
name: 'PolarAreaChart',
props: {
chartId: {
type: String,
default: 'line-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'polarArea'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(PolarArea), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { Chart as ChartJS, Filler, Legend, LineElement, PointElement, RadialLinearScale, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Radar } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, PointElement, RadialLinearScale, LineElement, Filler)
export default defineComponent({
name: 'RadarChart',
props: {
chartId: {
type: String,
default: 'radar-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'radar'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Radar), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,58 +0,0 @@
import type { PluginOptionsByType } from 'chart.js'
import { CategoryScale, Chart as ChartJS, Legend, LineElement, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Scatter } from 'vue-chartjs'
ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, CategoryScale, LinearScale)
export default defineComponent({
name: 'ScatterChart',
props: {
chartId: {
type: String,
default: 'scatter-chart',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object as PropType<Partial<CSSStyleDeclaration>>,
default: () => ({}),
},
plugins: {
type: Array as PropType<PluginOptionsByType<'scatter'>[]>,
default: () => [],
},
chartData: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
},
setup(props) {
return () =>
h(h(Scatter), {
data: props.chartData,
options: props.chartOptions,
chartId: props.chartId,
width: props.width,
height: props.height,
cssClasses: props.cssClasses,
styles: props.styles,
plugins: props.plugins,
})
},
})

View File

@ -1,139 +0,0 @@
@use "mixins";
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
@use "@layouts/styles/placeholders";
@use "@configured-variables" as variables;
// 👉 Avatar group
.v-avatar-group {
display: flex;
align-items: center;
> * {
&:not(:first-child) {
margin-inline-start: -0.8rem;
}
transition: transform 0.25s ease, box-shadow 0.15s ease;
&:hover {
z-index: 2;
transform: translateY(-5px) scale(1.05);
@include mixins_elevation.elevation(3);
}
}
> .v-avatar {
border: 2px solid rgb(var(--v-theme-surface));
}
}
// 👉 Button outline with default color border color
.v-alert--variant-outlined,
.v-avatar--variant-outlined,
.v-btn.v-btn--variant-outlined,
.v-card--variant-outlined,
.v-chip--variant-outlined {
&:not([class*="text-"]) {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
&.text-default {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
}
// 👉 Custom Input
.custom-input {
padding: 1rem;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
opacity: 1;
transition: border-color 0.5s;
white-space: normal;
&:hover {
border-color: rgba(var(--v-border-color), 0.25);
}
&.active {
border-color: rgb(var(--v-theme-primary));
}
}
// Dialog responsive width
.v-dialog {
// dialog custom close btn
.v-dialog-close-btn {
position: absolute;
z-index: 1;
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity)) !important;
inset-block-start: 0.9375rem;
inset-inline-end: 0.9375rem;
}
.v-card {
@extend %style-scroll-bar;
}
}
@media (min-width: 576px) {
.v-dialog {
&.v-dialog-sm,
&.v-dialog-lg,
&.v-dialog-xl {
inline-size: 565px !important;
}
}
}
@media (min-width: 992px) {
.v-dialog {
&.v-dialog-lg,
&.v-dialog-xl {
inline-size: 865px !important;
}
}
}
@media (min-width: 1200px) {
.v-dialog.v-dialog-xl,
.v-dialog.v-dialog-xl .v-overlay__content > .v-card {
inline-size: 1165px !important;
}
}
// v-tab with pill support
.v-tabs.v-tabs-pill {
.v-tab.v-btn {
border-radius: 0.25rem !important;
transition: none;
.v-tab__slider {
visibility: hidden;
}
}
.v-slide-group__content {
transition: none;
}
}
// loop for all colors bg
@each $color-name in variables.$theme-colors-name {
.v-tabs.v-tabs-pill {
.v-slide-group-item--active.v-tab--selected.text-#{$color-name} {
background-color: rgb(var(--v-theme-#{$color-name}));
color: rgb(var(--v-theme-on-#{$color-name})) !important;
}
}
}
// We are make even width of all v-timeline body
.v-timeline--vertical.v-timeline {
.v-timeline-item {
.v-timeline-item__body {
justify-self: stretch !important;
}
}
}

View File

@ -1,16 +0,0 @@
@use "@configured-variables" as variables;
//
//* Perfect Scrollbar
//
.v-application.v-theme--dark {
.ps__rail-y,
.ps__rail-x {
background-color: transparent !important;
}
.ps__thumb-y {
background-color: variables.$plugin-ps-thumb-y-dark;
}
}

View File

@ -1,45 +0,0 @@
@use "vuetify/lib/styles/tools/elevation" as elevation;
@use "@/plugins/vuetify/@core/scss/base/placeholders" as *;
@use "@/plugins/vuetify/@core/scss/template/placeholders" as *;
.layout-wrapper.layout-nav-type-horizontal {
.layout-navbar-and-nav-container {
@extend %default-layout-horizontal-nav-navbar-and-nav-container;
}
// 👉 Navbar
.layout-navbar {
@extend %default-layout-horizontal-nav-navbar;
}
// 👉 Layout content container
.navbar-content-container {
display: flex;
align-items: center;
block-size: 100%;
}
.layout-horizontal-nav {
@extend %default-layout-horizontal-nav-nav;
.nav-items {
@extend %default-layout-horizontal-nav-nav-items-list;
}
}
// 👉 App footer
.layout-footer {
@at-root {
.layout-footer-sticky#{&} {
background-color: rgb(var(--v-theme-surface));
@include elevation.elevation(3);
}
}
}
// TODO: Use Vuetify grid sass variable here
.layout-page-content {
padding-block: 1.5rem;
}
}

View File

@ -1,103 +0,0 @@
@use "@configured-variables" as variables;
@use "@/plugins/vuetify/@core/scss/base/placeholders" as *;
@use "@/plugins/vuetify/@core/scss/template/placeholders" as *;
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
@use "misc";
$header: ".layout-navbar";
@if variables.$layout-vertical-nav-navbar-is-contained {
$header: ".layout-navbar .navbar-content-container";
}
.layout-wrapper.layout-nav-type-vertical {
// SECTION Layout Navbar
// 👉 Elevated navbar
@if variables.$vertical-nav-navbar-style == "elevated" {
// Add transition
#{$header} {
transition: padding 0.2s ease, background-color 0.18s ease;
}
// If navbar is contained => Add border radius to header
@if variables.$layout-vertical-nav-navbar-is-contained {
#{$header} {
border-radius: 0 0 variables.$default-layout-with-vertical-nav-navbar-footer-roundness variables.$default-layout-with-vertical-nav-navbar-footer-roundness;
}
}
// Scrolled styles for sticky navbar
@at-root {
/* This html selector with not selector is required when:
dialog is opened and window don't have any scroll. This removes window-scrolled class from layout and out style broke
*/
html.v-overlay-scroll-blocked:not([style*="--v-body-scroll-y:0px;"]) .layout-navbar-sticky,
&.window-scrolled.layout-navbar-sticky {
#{$header} {
@extend %default-layout-vertical-nav-scrolled-sticky-elevated-nav;
@extend %default-layout-vertical-nav-floating-navbar-and-sticky-elevated-navbar-scrolled;
}
.navbar-blur#{$header} {
@extend %blurry-bg;
}
}
}
}
// 👉 Floating navbar
@else if variables.$vertical-nav-navbar-style == "floating" {
// Regardless of navbar is contained or not => Apply overlay to .layout-navbar
.layout-navbar {
&.navbar-blur {
@extend %default-layout-vertical-nav-floating-navbar-overlay;
}
}
&:not(.layout-navbar-sticky) {
#{$header} {
margin-block-start: variables.$vertical-nav-floating-navbar-top;
}
}
#{$header} {
@if variables.$layout-vertical-nav-navbar-is-contained {
border-radius: variables.$default-layout-with-vertical-nav-navbar-footer-roundness;
}
background-color: rgb(var(--v-theme-surface));
@extend %default-layout-vertical-nav-floating-navbar-and-sticky-elevated-navbar-scrolled;
}
.navbar-blur#{$header} {
@extend %blurry-bg;
}
}
// !SECTION
// 👉 Layout footer
.layout-footer {
$ele-layout-footer: &;
.footer-content-container {
border-radius: variables.$default-layout-with-vertical-nav-navbar-footer-roundness variables.$default-layout-with-vertical-nav-navbar-footer-roundness 0 0;
// Sticky footer
@at-root {
// .layout-footer-sticky#{$ele-layout-footer} => .layout-footer-sticky.layout-wrapper.layout-nav-type-vertical .layout-footer
.layout-footer-sticky#{$ele-layout-footer} {
.footer-content-container {
background-color: rgb(var(--v-theme-surface));
padding-block: 0;
padding-inline: 1.2rem;
@include mixins_elevation.elevation(3);
}
}
}
}
}
}

View File

@ -1,8 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/placeholders";
@use "@configured-variables" as variables;
.layout-navbar {
@if variables.$navbar-high-emphasis-text {
@extend %layout-navbar;
}
}

View File

@ -1,189 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/placeholders" as *;
@use "@/plugins/vuetify/@core/scss/template/placeholders" as *;
@use "@configured-variables" as variables;
@use "@layouts/styles/mixins" as layoutsMixins;
@use "@/plugins/vuetify/@core/scss/base/mixins";
@use "vuetify/lib/styles/tools/states" as vuetifyStates;
.layout-horizontal-nav {
@extend %nav;
// 👉 Icon styles
.nav-item-icon {
@extend %horizontal-nav-item-icon;
}
// 👉 Common styles for nav group & nav link
.nav-link,
.nav-group {
// 👉 Disabled nav items
&.disabled {
opacity: var(--v-disabled-opacity);
pointer-events: none;
}
// Set width of inner nav group and link
&.sub-item {
@extend %horizontal-nav-subitem;
}
}
// SECTION Nav Link
.nav-link {
@extend %nav-link;
a {
@extend %horizontal-nav-item;
// Adds before psudo element to style hover state
@include mixins.before-pseudo;
// Adds vuetify states
@include vuetifyStates.states($active: false);
}
// 👉 Top level nav link
&:not(.sub-item) {
a {
@extend %horizontal-nav-top-level-item;
&.router-link-active {
@extend %nav-link-active;
}
}
}
// 👉 Sub link
&.sub-item {
a {
&.router-link-active {
// We will not use active styles from material here because we want to use primary color for active link
@extend %horizontal-nav-sub-nav-link-active;
}
}
}
}
// !SECTION
// SECTION Nav Group
.nav-group {
.popper-triggerer {
.nav-group-label {
@extend %horizontal-nav-item;
}
}
> .popper-triggerer > .nav-group-label {
// Adds before psudo element to style hover state
@include mixins.before-pseudo;
// Adds vuetify states
@include vuetifyStates.states($active: false);
}
// 👉 Top level group
&:not(.sub-item) {
> .popper-triggerer {
position: relative;
/*
The Bridge
This after pseudo will work as bridge when we have space between popper triggerer and popper content
Initially it will have pointer events none for normal behavior and once the content is shown it will
work as bridge by setting pointer events to `auto`
*/
&::after {
position: absolute;
block-size: variables.$horizontal-nav-popper-content-top;
content: "";
inline-size: 100%;
inset-block-start: 100%;
inset-inline-start: 0;
pointer-events: none;
}
}
// Enable the pseudo bridge when content is shown by setting pointer events to `auto`
&.show-content > .popper-triggerer::after {
/*
We have added `z-index: 2` because when there is horizontal nav item below the popper trigger (group)
without this style nav item below popper trigger (group) gets focus hence closes the popper content
*/
z-index: 2;
pointer-events: auto;
}
> .popper-triggerer > .nav-group-label {
@extend %horizontal-nav-top-level-item;
}
&.active {
> .popper-triggerer > .nav-group-label {
@extend %nav-link-active;
}
}
> .popper-content {
// Add space between popper wrapper & content
margin-block-start: variables.$horizontal-nav-popper-content-top !important;
}
}
// 👉 Sub group
&.sub-item {
&.active {
@include mixins.selected-states("> .popper-triggerer > .nav-group-label::before");
}
// Reduce the icon's size of nested group's nav links (Top level group > Sub group > [Nav links])
.sub-item {
.nav-item-icon {
@extend %third-level-nav-item-icon;
}
}
}
.nav-group-arrow {
font-size: 1.375rem;
/*
ml-auto won't matter in top level group (because we haven't specified fixed width for top level groups)
but we wrote generally because we don't want to become so specific
*/
margin-inline-start: auto;
}
&.popper-inline-end {
.nav-group-arrow {
transform: rotateZ(270deg);
@include layoutsMixins.rtl {
transform: rotateZ(90deg);
}
}
}
.nav-item-title {
@extend %horizontal-nav-item-title;
}
.popper-content {
@extend %horizontal-nav-popper-content-hidden;
@extend %horizontal-nav-popper-content;
background-color: rgb(var(--v-theme-surface));
// Set max-height for the popper content
> div {
max-block-size: variables.$horizontal-nav-popper-content-max-height;
}
}
&.show-content > .popper-content {
@extend %horizontal-nav-popper-content-visible;
}
}
// !SECTION
}

View File

@ -1,48 +0,0 @@
@use "sass:map";
// Layout
@use "vertical-nav";
@use "horizontal-nav";
@use "default-layout";
@use "default-layout-w-vertical-nav";
@use "default-layout-w-horizontal-nav";
// Layouts package
@use "layouts";
// Skins
@use "skins";
// Components
@use "components";
// Utilities
@use "utilities";
// Route Transitions
@use "route-transitions";
// Misc
@use "misc";
// Dark
@use "dark";
// libs
@use "libs/perfect-scrollbar";
a {
color: rgb(var(--v-theme-primary));
text-decoration: none;
}
// Vuetify 3 don't provide margin bottom style like vuetify 2
p {
margin-block-end: 1rem;
}
// Iconify icon size
svg.iconify {
block-size: 1em;
inline-size: 1em;
}

View File

@ -1,63 +0,0 @@
@use "@configured-variables" as variables;
/* This styles extends the existing layout package's styles for handling cases that aren't related to layouts package */
/*
When we use v-layout as immediate first child of `.page-content-container`, it adds display:flex and page doesn't get contained height
*/
// .layout-wrapper.layout-nav-type-vertical {
// &.layout-content-height-fixed {
// .page-content-container {
// > .v-layout:first-child > :not(.v-navigation-drawer):first-child {
// flex-grow: 1;
// block-size: 100%;
// }
// }
// }
// }
.layout-wrapper.layout-nav-type-vertical {
&.layout-content-height-fixed {
.page-content-container {
> .v-layout:first-child {
overflow: hidden;
min-block-size: 100%;
> .v-main {
// overflow-y: auto;
.v-main__wrap > :first-child {
block-size: 100%;
overflow-y: auto;
}
}
}
}
}
}
// Let div/v-layout take full height. E.g. Email App
.layout-wrapper.layout-nav-type-horizontal {
&.layout-content-height-fixed {
> .layout-page-content {
display: flex;
}
}
}
// 👉 Floating navbar styles
@if variables.$vertical-nav-navbar-style == "floating" {
// Add spacing above navbar if navbar is floating (was in %layout-navbar-sticky placeholder)
.layout-wrapper.layout-nav-type-vertical.layout-navbar-sticky {
.layout-navbar {
inset-block-start: variables.$vertical-nav-floating-navbar-top;
}
/*
If it's floating navbar
Add `vertical-nav-floating-navbar-top` as margin top to .layout-page-content
*/
.layout-page-content {
margin-block-start: variables.$vertical-nav-floating-navbar-top;
}
}
}

View File

@ -1,20 +0,0 @@
// scrollable-content allows creating fixed header and scrollable content for VNavigationDrawer (Used when perfect scrollbar is used)
.scrollable-content {
&.v-navigation-drawer {
.v-navigation-drawer__content {
display: flex;
overflow: hidden;
flex-direction: column;
}
}
}
// adding styling for code tag
code {
border-radius: 3px;
color: rgb(var(--v-code-color));
font-size: 90%;
font-weight: 400;
padding-block: 0.2em;
padding-inline: 0.4em;
}

View File

@ -1,73 +0,0 @@
// @use "@styles/variables/_vuetify.scss";
// This mixin is inspired from vuetify for adding hover styles via before pseudo element
@mixin before-pseudo() {
position: relative;
&::before {
position: absolute;
border-radius: inherit;
background: currentcolor;
block-size: 100%;
content: "";
inline-size: 100%;
inset: 0;
opacity: 0;
pointer-events: none;
}
}
@mixin bordered-skin($component, $border-property: "border", $important: false) {
#{$component} {
background-color: rgb(var(--v-theme-background));
box-shadow: none !important;
#{$border-property}: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)) if($important, !important, null);
}
}
// Inspired from vuetify's active-states mixin
// focus => 0.12 & selected => 0.08
@mixin selected-states($selector) {
// #{$selector} {
// opacity: calc(#{map.get(vuetify.$states, "selected")} * var(--v-theme-overlay-multiplier));
// }
// &:hover
// #{$selector} {
// opacity: calc(#{map.get(vuetify.$states, "selected") + map.get(vuetify.$states, "hover")} * var(--v-theme-overlay-multiplier));
// }
// &:focus-visible
// #{$selector} {
// opacity: calc(#{map.get(vuetify.$states, "selected") + map.get(vuetify.$states, "focus")} * var(--v-theme-overlay-multiplier));
// }
// @supports not selector(:focus-visible) {
// &:focus {
// #{$selector} {
// opacity: calc(#{map.get(vuetify.$states, "selected") + map.get(vuetify.$states, "focus")} * var(--v-theme-overlay-multiplier));
// }
// }
// }
#{$selector} {
opacity: calc(var(--v-selected-opacity) * var(--v-theme-overlay-multiplier));
}
&:hover
#{$selector} {
opacity: calc(var(--v-selected-opacity) + var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
}
&:focus-visible
#{$selector} {
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
}
@supports not selector(:focus-visible) {
&:focus {
#{$selector} {
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
}
}
}
}

View File

@ -1,70 +0,0 @@
// 👉 Zoom fade
.app-transition-zoom-fade-enter-active,
.app-transition-zoom-fade-leave-active {
transition: transform 0.35s, opacity 0.28s ease-in-out;
}
.app-transition-zoom-fade-enter-from {
opacity: 0;
transform: scale(0.98);
}
.app-transition-zoom-fade-leave-to {
opacity: 0;
transform: scale(1.02);
}
// 👉 Fade
.app-transition-fade-enter-active,
.app-transition-fade-leave-active {
transition: opacity 0.25s ease-in-out;
}
.app-transition-fade-enter-from,
.app-transition-fade-leave-to {
opacity: 0;
}
// 👉 Fade bottom
.app-transition-fade-bottom-enter-active,
.app-transition-fade-bottom-leave-active {
transition: opacity 0.3s, transform 0.35s;
}
.app-transition-fade-bottom-enter-from {
opacity: 0;
transform: translateY(-0.6rem);
}
.app-transition-fade-bottom-leave-to {
opacity: 0;
transform: translateY(0.6rem);
}
// 👉 Slide fade
.app-transition-slide-fade-enter-active,
.app-transition-slide-fade-leave-active {
transition: opacity 0.3s, transform 0.35s;
}
.app-transition-slide-fade-enter-from {
opacity: 0;
transform: translateX(-0.6rem);
}
.app-transition-slide-fade-leave-to {
opacity: 0;
transform: translateX(0.6rem);
}
// 👉 Zoom out
.app-transition-zoom-out-enter-active,
.app-transition-zoom-out-leave-active {
transition: opacity 0.26s ease-in-out, transform 0.3s ease-out;
}
.app-transition-zoom-out-enter-from,
.app-transition-zoom-out-leave-to {
opacity: 0;
transform: scale(0.98);
}

View File

@ -1,116 +0,0 @@
@use "@configured-variables" as variables;
@use "@layouts/styles/mixins" as layoutsMixins;
// 👉 Demo spacers
// TODO: Use vuetify SCSS variable here
$card-spacer-content: 16px;
.demo-space-x {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-block-start: -$card-spacer-content;
& > * {
margin-block-start: $card-spacer-content;
margin-inline-end: $card-spacer-content;
}
}
.demo-space-y {
& > * {
margin-block-end: $card-spacer-content;
&:last-child {
margin-block-end: 0;
}
}
}
// 👉 Card match height
.match-height.v-row {
.v-card {
block-size: 100%;
}
}
// 👉 Whitespace
.whitespace-no-wrap {
white-space: nowrap;
}
// 👉 Colors
/*
Vuetify is applying `.text-white` class to badge icon but don't provide its styles
Moreover, we also use this class in some places
In vuetify 2 with `$color-pack: false` SCSS var config this class was getting generated but this is not the case in v3
We also need !important to get correct color in badge icon
*/
.text-white {
color: #fff !important;
}
.bg-var-theme-background {
background-color: rgba(var(--v-theme-background), var(--v-medium-emphasis-opacity)) !important;
}
// [/^bg-light-(\w+)$/, ([, w]) => ({ backgroundColor: `rgba(var(--v-theme-${w}), var(--v-activated-opacity))` })],
@each $color-name in variables.$theme-colors-name {
.bg-light-#{$color-name} {
background-color: rgba(var(--v-theme-#{$color-name}), var(--v-activated-opacity)) !important;
}
}
// 👉 Typography
.font-weight-semibold {
font-weight: 600 !important;
}
.leading-normal {
line-height: normal !important;
}
// 👉 for rtl only
.flip-in-rtl {
@include layoutsMixins.rtl {
transform: scaleX(-1);
}
}
// 👉 Carousel
.carousel-delimiter-top-end {
.v-carousel__controls {
justify-content: end;
block-size: 40px;
inset-block-start: 0;
padding-inline: 1rem;
.v-btn--icon.v-btn--density-default {
block-size: calc(var(--v-btn-height) + -10px);
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
inline-size: calc(var(--v-btn-height) + -10px);
&.v-btn--active {
color: #fff;
}
.v-btn__overlay {
opacity: 0;
}
}
}
@each $color-name in variables.$theme-colors-name {
&.dots-active-#{$color-name} {
.v-carousel__controls {
.v-btn--active {
color: rgb(var(--v-theme-#{$color-name})) !important;
}
}
}
}
}

View File

@ -1,152 +0,0 @@
@use "sass:map";
@use "sass:list";
// Thanks: https://css-tricks.com/snippets/sass/deep-getset-maps/
@function map-deep-get($map, $keys...) {
@each $key in $keys {
$map: map.get($map, $key);
}
@return $map;
}
@function map-deep-set($map, $keys, $value) {
$maps: ($map,);
$result: null;
// If the last key is a map already
// Warn the user we will be overriding it with $value
@if type-of(nth($keys, -1)) == "map" {
@warn "The last key you specified is a map; it will be overrided with `#{$value}`.";
}
// If $keys is a single key
// Just merge and return
@if length($keys) == 1 {
@return map-merge($map, ($keys: $value));
}
// Loop from the first to the second to last key from $keys
// Store the associated map to this key in the $maps list
// If the key doesn't exist, throw an error
@for $i from 1 through length($keys) - 1 {
$current-key: list.nth($keys, $i);
$current-map: list.nth($maps, -1);
$current-get: map.get($current-map, $current-key);
@if not $current-get {
@error "Key `#{$key}` doesn't exist at current level in map.";
}
$maps: list.append($maps, $current-get);
}
// Loop from the last map to the first one
// Merge it with the previous one
@for $i from length($maps) through 1 {
$current-map: list.nth($maps, $i);
$current-key: list.nth($keys, $i);
$current-val: if($i == list.length($maps), $value, $result);
$result: map.map-merge($current-map, ($current-key: $current-val));
}
// Return result
@return $result;
}
// font size utility classes
// font size
$font-sizes: (
"xs": 0.75rem,
"sm": 0.875rem,
"base": 1rem,
"lg": 1.125rem,
"xl": 1.25rem,
"2xl": 1.5rem,
"3xl": 1.875rem,
"4xl": 2.25rem,
"5xl": 3rem,
"6xl": 3.75rem,
"7xl": 4.5rem,
"8xl": 6rem,
"9xl": 8rem
);
// font line-height
$font-line-height: (
"xs": 1rem,
"sm": 1.25rem,
"base": 1.5rem,
"lg": 1.75rem,
"xl": 1.75rem,
"2xl": 2rem,
"3xl": 2.25rem,
"4xl": 2.5rem,
"5xl": 1,
"6xl": 1,
"7xl": 1,
"8xl": 1,
"9xl": 1
);
@each $name, $size in $font-sizes {
.text-#{$name} {
font-size: $size;
line-height: map.get($font-line-height, $name);
}
}
// truncate utility class
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// gap utility class
$gap: (
"0": 0,
"1": 0.25rem,
"2": 0.5rem,
"3": 0.75rem,
"4": 1rem,
"5": 1.25rem,
"6":1.5rem,
"7": 1.75rem,
"8": 2rem,
"9": 2.25rem,
"10": 2.5rem,
"11": 2.75rem,
"12": 3rem,
"14": 3.5rem,
"16": 4rem,
"20": 5rem,
"24": 6rem,
"28": 7rem,
"32": 8rem,
"36": 9rem,
"40": 10rem,
"44": 11rem,
"48": 12rem,
"52": 13rem,
"56": 14rem,
"60": 15rem,
"64": 16rem,
"72": 18rem,
"80": 20rem,
"96": 24rem
);
@each $name, $size in $gap {
.gap-#{$name} {
gap: $size;
}
.gap-x-#{$name} {
column-gap: $size;
}
.gap-y-#{$name} {
row-gap: $size;
}
}

View File

@ -1,141 +0,0 @@
/*
TODO: Add docs on when to use placeholder vs when to use SASS variable
Placeholder
- When we want to keep customization to our self between templates use it
Variables
- When we want to allow customization from both user and our side
- You can also use variable for consistency (e.g. mx 1 rem should be applied to both vertical nav items and vertical nav header)
*/
@forward "@layouts/styles/variables" with (
// Adjust z-index so vertical nav & overlay stays on top of v-layout in v-main. E.g. Email app
$layout-vertical-nav-z-index: 1004,
$layout-overlay-z-index: 1003,
);
@use "@layouts/styles/variables" as *;
// 👉 Default layout
$navbar-high-emphasis-text: true !default;
// @forward "@layouts/styles/variables" with (
// $layout-vertical-nav-width: 350px !default,
// );
$css-vars: (
/*
- Skins
- CSS var
- Theme
*/
"default": (
"--v-theme-background": (
"light": (244 ,245, 250),
"dark": (40 ,36, 61),
),
"--v-theme-surface": (
"light": (255, 255, 255),
"dark": (49, 45, 75),
),
),
"bordered": (
"--v-theme-background": (
"light": (255 ,255, 255),
"dark": (49, 45, 75),
),
"--v-theme-surface": (
"light": (255, 255, 255),
"dark": (49, 45, 75),
),
),
) !default;
$theme-colors-name: (
"primary",
"secondary",
"error",
"info",
"success",
"warning"
) !default;
// 👉 Default layout with vertical nav
$default-layout-with-vertical-nav-navbar-footer-roundness: 10px !default;
// 👉 Vertical nav
$vertical-nav-background-color-rgb: var(--v-theme-background) !default;
$vertical-nav-background-color: rgb(#{$vertical-nav-background-color-rgb}) !default;
// This is used to keep consistency between nav items and nav header left & right margin
// This is used by nav items & nav header
$vertical-nav-horizontal-spacing: 1rem !default;
$vertical-nav-horizontal-padding: 0.75rem !default;
// Vertical nav header height. Mostly we will align it with navbar height;
$vertical-nav-header-height: $layout-vertical-nav-navbar-height !default;
$vertical-nav-navbar-elevation: 3 !default;
$vertical-nav-navbar-style: "elevated" !default; // options: elevated, floating
$vertical-nav-floating-navbar-top: 1rem !default;
// Vertical nav header padding
$vertical-nav-header-padding: 1rem $vertical-nav-horizontal-padding !default;
$vertical-nav-header-inline-spacing: $vertical-nav-horizontal-spacing !default;
// Move logo when vertical nav is mini (collapsed but not hovered)
$vertical-nav-header-logo-translate-x-when-vertical-nav-mini: -4px !default;
// Space between logo and title
$vertical-nav-header-logo-title-spacing: 0.9rem !default;
// Section title margin top (when its not first child)
$vertical-nav-section-title-mt: 1.5rem !default;
// Section title margin bottom
$vertical-nav-section-title-mb: 0.5rem !default;
// Vertical nav icons
$vertical-nav-items-icon-size: 1.5rem !default;
$vertical-nav-items-nested-icon-size: 0.9rem !default;
$vertical-nav-items-icon-margin-inline-end: 0.5rem !default;
// Transition duration for nav group arrow
$vertical-nav-nav-group-arrow-transition-duration: 0.15s !default;
// Timing function for nav group arrow
$vertical-nav-nav-group-arrow-transition-timing-function: ease-in-out !default;
// 👉 Horizontal nav
/*
Heads up
==================
Here we assume we will always use shorthand property which will apply same padding on four side
This is because this have been used as value of top property by `.popper-content`
*/
$horizontal-nav-padding: 0.6875rem !default;
// Gap between top level horizontal nav items
$horizontal-nav-top-level-items-gap: 4px !default;
// Horizontal nav icons
$horizontal-nav-items-icon-size: 1.5rem !default;
$horizontal-nav-third-level-icon-size: 0.9rem !default;
$horizontal-nav-items-icon-margin-inline-end: 0.625rem !default;
// We used SCSS variable because we want to allow users to update max height of popper content
// 120px is combined height of navbar & horizontal nav
$horizontal-nav-popper-content-max-height: calc((var(--vh, 1vh) * 100) - 120px - 4rem) !default;
// This variable is used for horizontal nav popper content's `margin-top` and "The bridge"'s height. We need to sync both values.
$horizontal-nav-popper-content-top: calc($horizontal-nav-padding + 0.375rem) !default;
// 👉 Plugins
$plugin-ps-thumb-y-dark: rgba(var(--v-theme-surface-variant), 0.35) !default;
// 👉 Vuetify
// Used in src/@core/scss/base/libs/vuetify/_overrides.scss
$vuetify-reduce-default-compact-button-icon-size: true !default;

View File

@ -1,245 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/placeholders" as *;
@use "@/plugins/vuetify/@core/scss/template/placeholders" as *;
@use "@layouts/styles/mixins" as layoutsMixins;
@use "@configured-variables" as variables;
@use "@/plugins/vuetify/@core/scss/base/mixins" as mixins;
@use "vuetify/lib/styles/tools/states" as vuetifyStates;
@use "vuetify/lib/styles/tools/elevation" as elevation;
.layout-nav-type-vertical {
// 👉 Layout Vertical nav
.layout-vertical-nav {
$sl-layout-nav-type-vertical: &;
@extend %nav;
@at-root {
// Add styles for collapsed vertical nav
.layout-vertical-nav-collapsed#{$sl-layout-nav-type-vertical}.hovered {
@include elevation.elevation(6);
}
}
background-color: variables.$vertical-nav-background-color;
// 👉 Nav header
.nav-header {
overflow: hidden;
padding: variables.$vertical-nav-header-padding;
margin-inline: variables.$vertical-nav-header-inline-spacing;
min-block-size: variables.$vertical-nav-header-height;
// TEMPLATE: Check if we need to move this to master
.app-logo {
flex-shrink: 0;
transition: transform 0.25s ease-in-out;
@at-root {
// Move logo a bit to align center with the icons in vertical nav mini variant
.layout-vertical-nav-collapsed#{$sl-layout-nav-type-vertical}:not(.hovered) .nav-header .app-logo {
transform: translateX(variables.$vertical-nav-header-logo-translate-x-when-vertical-nav-mini);
@include layoutsMixins.rtl {
transform: translateX(-(variables.$vertical-nav-header-logo-translate-x-when-vertical-nav-mini));
}
}
}
}
.app-title {
margin-inline-start: variables.$vertical-nav-header-logo-title-spacing;
}
.header-action {
@extend %nav-header-action;
}
}
// 👉 Nav items shadow
.vertical-nav-items-shadow {
position: absolute;
z-index: 1;
background:
linear-gradient(
rgb(#{variables.$vertical-nav-background-color-rgb}) 5%,
rgba(#{variables.$vertical-nav-background-color-rgb}, 75%) 45%,
rgba(#{variables.$vertical-nav-background-color-rgb}, 20%) 80%,
transparent
);
block-size: 55px;
inline-size: 100%;
inset-block-start: calc(#{variables.$vertical-nav-header-height} - 2px);
opacity: 0;
pointer-events: none;
transform: translateX(-8px);
transition: opacity 0.15s ease-in-out;
will-change: opacity;
@include layoutsMixins.rtl {
transform: translateX(8px);
}
}
&.scrolled {
.vertical-nav-items-shadow {
opacity: 1;
}
}
// 👉 Nav section title
.nav-section-title {
@extend %vertical-nav-item;
@extend %vertical-nav-section-title;
margin-block-end: variables.$vertical-nav-section-title-mb;
&:not(:first-child) {
margin-block-start: variables.$vertical-nav-section-title-mt;
}
.placeholder-icon {
margin-inline: auto;
}
}
// Nav item badge
.nav-item-badge {
@extend %vertical-nav-item-badge;
}
// 👉 Nav group & Link
.nav-link,
.nav-group {
overflow: hidden;
> :first-child {
@extend %vertical-nav-item;
@extend %vertical-nav-item-interactive;
}
.nav-item-icon {
@extend %vertical-nav-items-icon;
}
&.disabled {
opacity: var(--v-disabled-opacity);
pointer-events: none;
}
}
// 👉 Vertical nav link
.nav-link {
@extend %nav-link;
> .router-link-exact-active {
@extend %nav-link-active;
}
> a {
// Adds before psudo element to style hover state
@include mixins.before-pseudo;
// Adds vuetify states
@include vuetifyStates.states($active: false);
}
}
// 👉 Vertical nav group
.nav-group {
// Reduce the size of icon if link/group is inside group
.nav-group,
.nav-link {
.nav-item-icon {
@extend %vertical-nav-items-nested-icon;
}
}
// Hide icons after 2nd level
& .nav-group {
.nav-link,
.nav-group {
.nav-item-icon {
@extend %vertical-nav-items-icon-after-2nd-level;
}
}
}
.nav-group-arrow {
flex-shrink: 0;
transform-origin: center;
transition: transform variables.$vertical-nav-nav-group-arrow-transition-duration variables.$vertical-nav-nav-group-arrow-transition-timing-function;
will-change: transform;
}
// Rotate arrow icon if group is opened
&.open {
> .nav-group-label .nav-group-arrow {
transform: rotateZ(90deg);
}
}
// Nav group label
> :first-child {
// Adds before psudo element to style hover state
@include mixins.before-pseudo;
// Adds vuetify states
@include vuetifyStates.states($active: false);
}
// Active & open states for nav group label
&.active,
&.open {
> :first-child {
@extend %vertical-nav-group-open-active;
}
}
}
}
}
// 👉 Transitions
.vertical-nav-section-title-enter-active,
.vertical-nav-section-title-leave-active {
transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
}
.vertical-nav-section-title-enter-from,
.vertical-nav-section-title-leave-to {
opacity: 0;
transform: translateX(15px);
@include layoutsMixins.rtl {
transform: translateX(-15px);
}
}
.transition-slide-x-enter-active,
.transition-slide-x-leave-active {
transition: opacity 0.1s ease-in-out, transform 0.12s ease-in-out;
}
.transition-slide-x-enter-from,
.transition-slide-x-leave-to {
opacity: 0;
transform: translateX(-15px);
@include layoutsMixins.rtl {
transform: translateX(15px);
}
}
.vertical-nav-app-title-enter-active,
.vertical-nav-app-title-leave-active {
transition: opacity 0.1s ease-in-out, transform 0.12s ease-in-out;
}
.vertical-nav-app-title-enter-from,
.vertical-nav-app-title-leave-to {
opacity: 0;
transform: translateX(-15px);
@include layoutsMixins.rtl {
transform: translateX(15px);
}
}

View File

@ -1,35 +0,0 @@
$ps-size: 0.25rem;
$ps-hover-size: 0.375rem;
$ps-track-size: 0.5rem;
.ps__thumb-y {
inline-size: $ps-size;
inset-inline-end: 0.0625rem;
}
.ps__thumb-x {
block-size: $ps-size !important;
}
.ps__rail-x {
background: transparent !important;
block-size: $ps-track-size;
}
.ps__rail-y {
background: transparent !important;
inline-size: $ps-track-size !important;
inset-inline-end: 0.125rem !important;
inset-inline-start: unset !important;
}
.ps__rail-y.ps--clicking .ps__thumb-y,
.ps__rail-y:focus > .ps__thumb-y,
.ps__rail-y:hover > .ps__thumb-y {
inline-size: $ps-hover-size;
}
.ps__thumb-x,
.ps__thumb-y {
background-color: rgb(var(--v-theme-perfect-scrollbar-thumb)) !important;
}

View File

@ -1 +0,0 @@
@use "overrides";

View File

@ -1,243 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/utils";
@use "@configured-variables" as variables;
// 👉 Application
// We need accurate vh in mobile devices as well
.v-application__wrap {
/* stylelint-disable-next-line liberty/use-logical-spec */
min-height: calc(var(--vh, 1vh) * 100);
}
// 👉 Typography
h1,
h2,
h3,
h4,
h5,
h6,
.text-h1,
.text-h2,
.text-h3,
.text-h4,
.text-h5,
.text-h6,
.text-button,
.text-overline,
.v-card-title {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.v-application,
.text-body-1,
.text-body-2,
.text-subtitle-1,
.text-subtitle-2 {
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
}
// 👉 Grid
// Remove margin-bottom of v-input_details inside grid (validation error message)
.v-row {
.v-col,
[class^="v-col-*"] {
.v-input__details {
margin-block-end: 0;
}
}
}
// 👉 Theme
.v-theme--light {
--v-theme-background:
var(
--skin-theme-background,
#{utils.map-deep-get(variables.$css-vars, "default", "--v-theme-background", "light")}
) !important;
--v-theme-surface:
var(
--skin-theme-surface,
#{utils.map-deep-get(variables.$css-vars, "default", "--v-theme-surface", "light")}
) !important;
}
.v-theme--dark {
--v-theme-background:
var(
--skin-theme-background,
#{utils.map-deep-get(variables.$css-vars, "default", "--v-theme-background", "dark")}
) !important;
--v-theme-surface:
var(
--skin-theme-surface,
#{utils.map-deep-get(variables.$css-vars, "default", "--v-theme-surface", "dark")}
) !important;
}
// 👉 Button
@if variables.$vuetify-reduce-default-compact-button-icon-size {
.v-btn--density-compact.v-btn--size-default {
.v-btn__content > svg {
width: 22px;
height: 22px;
font-size: 22px;
}
}
}
// 👉 Card
// Removes padding-top for immediately placed v-card-text after itself
.v-card-text {
& + & {
padding-block-start: 0 !important;
}
}
/*
👉 Checkbox & Radio Ripple
TODO Checkbox and switch component. Remove it when vuetify resolve the extra spacing: https://github.com/vuetifyjs/vuetify/issues/15519
We need this because form elements likes checkbox and switches are by default set to height of textfield height which is way big than we want
Tested with checkbox & switches
*/
.v-checkbox.v-input,
.v-switch.v-input {
--v-input-control-height: auto;
flex: unset;
}
.v-selection-control--density-comfortable {
&.v-checkbox-btn,
&.v-radio,
&.v-radio-btn {
.v-selection-control__wrapper {
margin-inline-start: -0.5625rem;
}
}
}
.v-selection-control--density-compact {
&.v-radio,
&.v-radio-btn,
&.v-checkbox-btn {
.v-selection-control__wrapper {
margin-inline-start: -0.3125rem;
}
}
}
.v-selection-control--density-default {
&.v-checkbox-btn,
&.v-radio,
&.v-radio-btn {
.v-selection-control__wrapper {
margin-inline-start: -0.6875rem;
}
}
}
.v-radio-group {
.v-selection-control-group {
.v-radio:not(:last-child) {
margin-inline-end: 0.9rem;
}
}
}
/*
👉 Tabs
Disable tab transition
This is for tabs where we don't have card wrapper to tabs and have multiple cards as tab content.
This class will disable transition and adds `overflow: unset` on `VWindow` to allow spreading shadow
*/
.disable-tab-transition {
overflow: unset !important;
.v-window__container {
block-size: auto !important;
}
.v-window-item:not(.v-window-item--active) {
display: none !important;
}
.v-window__container .v-window-item {
transform: none !important;
}
}
// 👉 List
.v-list {
// Set icons opacity to .87
.v-list-item__prepend > .v-icon,
.v-list-item__append > .v-icon {
opacity: var(--v-high-emphasis-opacity);
}
}
// 👉 Card list
/*
Custom class
Remove list spacing inside card
This is because card title gets padding of 20px and list item have padding of 16px. Moreover, list container have padding-bottom as well.
*/
.card-list {
--v-card-list-gap: 20px;
&.v-list {
padding-block: 0;
}
.v-list-item {
min-block-size: unset;
min-block-size: auto !important;
padding-block: 0 !important;
padding-inline: 0 !important;
> .v-ripple__container {
opacity: 0;
}
&:not(:last-child) {
padding-block-end: var(--v-card-list-gap) !important;
}
}
.v-list-item:hover,
.v-list-item:focus,
.v-list-item:active,
.v-list-item.active {
> .v-list-item__overlay {
opacity: 0 !important;
}
}
}
// 👉 Table
.v-table {
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
}
// 👉 v-field
.v-field:hover .v-field__outline {
--v-field-border-opacity: var(--v-medium-emphasis-opacity);
}
// 👉 VLabel
.v-label {
opacity: 1;
&:not(.v-field-label--floating) {
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
}
}
// 👉 Overlay
.v-overlay__scrim {
background: rgba(var(--v-overlay-scrim-background), var(--v-overlay-scrim-opacity));
}

View File

@ -1,55 +0,0 @@
// 👉 Shadow opacities
$shadow-key-umbra-opacity-custom: var(--v-shadow-key-umbra-opacity);
$shadow-key-penumbra-opacity-custom: var(--v-shadow-key-penumbra-opacity);
$shadow-key-ambient-opacity-custom: var(--v-shadow-key-ambient-opacity);
// 👉 Card transition properties
$card-transition-property-custom: box-shadow, opacity;
@forward "vuetify/settings" with (
// 👉 General settings
$color-pack: false !default,
// 👉 Shadow opacity
$shadow-key-umbra-opacity: $shadow-key-umbra-opacity-custom !default,
$shadow-key-penumbra-opacity: $shadow-key-penumbra-opacity-custom !default,
$shadow-key-ambient-opacity: $shadow-key-ambient-opacity-custom !default,
// States
$states: (
"hover": 0.08,
"focus": 0.1,
"selected": 0.12,
"activated": 0.1,
"pressed": 0.14,
"dragged": 0.1
) !default,
// 👉 Card
$card-color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !default,
$card-elevation: 6 !default,
$card-title-line-height: 1.6 !default,
$card-actions-min-height: unset !default,
$card-text-padding: 1.25rem !default,
$card-item-padding: 1.25rem !default,
$card-actions-padding: 0 12px 12px !default,
$card-transition-property: $card-transition-property-custom !default,
$card-subtitle-opacity: 1 !default,
// 👉 Expansion Panel
$expansion-panel-active-title-min-height: 48px !default,
// 👉 List
$list-item-icon-margin-end: 16px !default,
$list-item-icon-margin-start: 16px !default,
// 👉 Tooltip
$tooltip-background-color: rgba(59, 55, 68, 0.9) !default,
$tooltip-text-color: rgb(var(--v-theme-on-primary)) !default,
$tooltip-font-size: 0.75rem !default,
$button-icon-density: ("default": 2, "comfortable": 0, "compact": -1 ) !default,
// 👉 VTimeline
$timeline-dot-size: 34px !default,
);

View File

@ -1,27 +0,0 @@
@use "vuetify/lib/styles/tools/elevation" as elevation;
@use "@configured-variables" as variables;
@use "misc";
%default-layout-horizontal-nav-navbar-and-nav-container {
@include elevation.elevation(3);
// 1000 is v-window z-index
z-index: 1001;
background-color: rgb(var(--v-theme-surface));
&.header-blur {
@extend %blurry-bg;
}
}
%default-layout-horizontal-nav-navbar {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
%default-layout-horizontal-nav-nav {
padding-block: variables.$horizontal-nav-padding;
}
%default-layout-horizontal-nav-nav-items-list {
gap: variables.$horizontal-nav-top-level-items-gap;
}

View File

@ -1,46 +0,0 @@
@use "vuetify/lib/styles/tools/elevation" as elevation;
@use "@configured-variables" as variables;
@use "misc";
%default-layout-vertical-nav-scrolled-sticky-elevated-nav {
background-color: rgb(var(--v-theme-surface));
}
%default-layout-vertical-nav-floating-navbar-and-sticky-elevated-navbar-scrolled {
@include elevation.elevation(variables.$vertical-nav-navbar-elevation);
// If navbar is contained => Squeeze navbar content on scroll
@if variables.$layout-vertical-nav-navbar-is-contained {
padding-inline: 1.2rem;
}
}
%default-layout-vertical-nav-floating-navbar-overlay {
isolation: isolate;
&::after {
position: absolute;
z-index: -1;
/* stylelint-disable property-no-vendor-prefix */
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
/* stylelint-enable */
background:
linear-gradient(
180deg,
rgba(var(--v-theme-background), 70%) 44%,
rgba(var(--v-theme-background), 43%) 73%,
rgba(var(--v-theme-background), 0%)
);
background-repeat: repeat;
block-size: calc(variables.$layout-vertical-nav-navbar-height + variables.$vertical-nav-floating-navbar-top + 0.5rem);
content: "";
inset-block-start: -(variables.$vertical-nav-floating-navbar-top);
inset-inline-end: 0;
inset-inline-start: 0;
/* stylelint-disable property-no-vendor-prefix */
-webkit-mask: linear-gradient(black, black 18%, transparent 100%);
mask: linear-gradient(black, black 18%, transparent 100%);
/* stylelint-enable */
}
}

View File

@ -1,3 +0,0 @@
%layout-navbar {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
}

View File

@ -1,94 +0,0 @@
@use "@layouts/styles/mixins" as layoutsMixins;
@use "vuetify/lib/styles/tools/elevation" as elevation;
@use "@configured-variables" as variables;
@use "@layouts/styles/placeholders";
// Horizontal nav item styles (including nested)
%horizontal-nav-item {
padding-block: 0.563rem;
padding-inline: 1rem;
}
// Top level horizontal nav item styles (`a` tag & group label)
%horizontal-nav-top-level-item {
border-radius: 0.4rem;
}
// Active styles for sub nav link
%horizontal-nav-sub-nav-link-active {
background: rgba(var(--v-theme-primary), 0.1);
color: rgb(var(--v-theme-primary));
}
/*
This style is required when you don't provide any transition to horizontal nav items via themeConfig `themeConfig.horizontalNav.transition`
Also, you have to disable it if you are using transition
*/
// Popper content styles when it's hidden
%horizontal-nav-popper-content-hidden {
// display: none;
// opacity: 0;
// pointer-events: none;
// transform: translateY(7px);
// transition: transform 0.25s ease-in-out, opacity 0.15s ease-in-out;
}
/*
This style is required when you don't provide any transition to horizontal nav items via themeConfig `themeConfig.horizontalNav.transition`
Also, you have to disable it if you are using transition
*/
// Popper content styles when it's shown
%horizontal-nav-popper-content-visible {
// display: block;
// opacity: 1;
// pointer-events: auto;
// pointer-events: auto;
// transform: translateY(0);
}
// Horizontal nav item icon (Including sub nav items)
%horizontal-nav-item-icon {
font-size: variables.$horizontal-nav-items-icon-size;
margin-inline-end: variables.$horizontal-nav-items-icon-margin-inline-end;
}
// Horizontal nav subitem
%horizontal-nav-subitem {
min-inline-size: 12rem;
.nav-item-title {
margin-inline-end: 1rem;
}
}
// Styles for third level item icon/ (e.g. Reduce the icon's size of nested group's nav links (Top level group > Sub group > [Nav links]))
%third-level-nav-item-icon {
font-size: variables.$horizontal-nav-third-level-icon-size;
margin-inline-end: 0.75rem;
/*
`margin-inline` will be (normal icon font-size - small icon font-size) / 2
(1.5rem - 0.9rem) / 2 => 0.6rem / 2 => 0.3rem
*/
margin-inline-start: calc((variables.$horizontal-nav-items-icon-size - variables.$horizontal-nav-third-level-icon-size) / 2);
}
// Horizontal nav item title
%horizontal-nav-item-title {
margin-inline-end: 0.3rem;
white-space: nowrap;
}
// Popper content styles
%horizontal-nav-popper-content {
@include elevation.elevation(4);
border-radius: 6px;
padding-block: 0.3rem;
> div {
@extend %style-scroll-bar;
}
}

View File

@ -1,7 +0,0 @@
@forward "horizontal-nav";
@forward "vertical-nav";
@forward "nav";
@forward "default-layout";
@forward "default-layout-vertical-nav";
@forward "default-layout-horizontal-nav";
@forward "misc";

View File

@ -1,7 +0,0 @@
%blurry-bg {
/* stylelint-disable property-no-vendor-prefix */
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
/* stylelint-enable */
background-color: rgb(var(--v-theme-surface), 0.9);
}

View File

@ -1,33 +0,0 @@
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
// This is common style that needs to be applied to both navs
%nav {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
.nav-item-title {
letter-spacing: 0.15px;
}
.nav-section-title {
letter-spacing: 0.4px;
}
}
/*
Active nav link styles for horizontal & vertical nav
For horizontal nav it will be only applied to top level nav items
For vertical nav it will be only applied to nav links (not nav groups)
*/
%nav-link-active {
background-color: rgb(var(--v-global-theme-primary));
color: rgb(var(--v-theme-on-primary));
@include mixins_elevation.elevation(3);
}
%nav-link {
a {
color: inherit;
}
}

View File

@ -1,81 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/mixins";
@use "@configured-variables" as variables;
@use "vuetify/lib/styles/tools/states" as vuetifyStates;
%nav-header-action {
font-size: 1.25rem;
}
// Nav items styles (including section title)
%vertical-nav-item {
margin-block: 0;
margin-inline: variables.$vertical-nav-horizontal-spacing;
padding-block: 0;
padding-inline: variables.$vertical-nav-horizontal-padding;
white-space: nowrap;
}
// This is same as `%vertical-nav-item` except section title is excluded
%vertical-nav-item-interactive {
border-radius: 0.4rem;
block-size: 2.75rem;
/*
We will use `margin-block-end` instead of `margin-block` to give more space for shadow to appear.
With `margin-block`, due to small space (space gets divided between top & bottom) shadow cuts
*/
margin-block-end: 0.375rem;
}
// Common styles for nav item icon styles
// Nav group's children icon styles are not here (Adjusts height, width & margin)
%vertical-nav-items-icon {
flex-shrink: 0;
font-size: variables.$vertical-nav-items-icon-size;
margin-inline-end: variables.$vertical-nav-items-icon-margin-inline-end;
}
// Icon styling for icon nested inside another nav item (2nd level)
%vertical-nav-items-nested-icon {
/*
`margin-inline` will be (normal icon font-size - small icon font-size) / 2
(1.5rem - 0.9rem) / 2 => 0.6rem / 2 => 0.3rem
*/
$vertical-nav-items-nested-icon-margin-inline: calc((variables.$vertical-nav-items-icon-size - variables.$vertical-nav-items-nested-icon-size) / 2);
font-size: variables.$vertical-nav-items-nested-icon-size;
margin-inline-end: $vertical-nav-items-nested-icon-margin-inline + variables.$vertical-nav-items-icon-margin-inline-end;
margin-inline-start: $vertical-nav-items-nested-icon-margin-inline;
}
%vertical-nav-items-icon-after-2nd-level {
visibility: hidden;
}
// Open & Active nav group styles
%vertical-nav-group-open-active {
@include mixins.selected-states("&::before");
}
// Section title
%vertical-nav-section-title {
// Setting height will prevent jerking when text & icon is toggled
block-size: 1.5rem;
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
font-size: 0.75rem;
text-transform: uppercase;
}
// Vertical nav item badge styles
%vertical-nav-item-badge {
display: inline-block;
border-radius: 1.5rem;
font-size: 0.8em;
font-weight: 500;
line-height: 1;
padding-block: 0.25em;
padding-inline: 0.55em;
text-align: center;
vertical-align: baseline;
white-space: nowrap;
}

View File

@ -1,90 +0,0 @@
@use "sass:map";
@use "@/plugins/vuetify/@core/scss/base/mixins";
@use "@configured-variables" as variables;
@use "../utils";
$header: ".layout-navbar";
@if variables.$layout-vertical-nav-navbar-is-contained {
$header: ".layout-navbar .navbar-content-container";
}
.skin--bordered {
@include mixins.bordered-skin(".v-card:not(.v-card .v-card):not(.v-card--flat)");
@include mixins.bordered-skin(".v-menu .v-overlay__content > .v-card, .v-menu .v-overlay__content > .v-sheet, .v-menu .v-overlay__content > .v-list");
@include mixins.bordered-skin(".popper-content");
// Navbar
// -- Horizontal
@include mixins.bordered-skin(".layout-navbar-and-nav-container", "border-bottom");
// -- Vertical
// We have added `.layout-navbar-sticky` as well in selector because we don't want to add borders if navbar is static
@if variables.$layout-vertical-nav-navbar-is-contained {
@include mixins.bordered-skin(".layout-nav-type-vertical.window-scrolled.layout-navbar-sticky #{$header}");
.layout-nav-type-vertical.window-scrolled #{$header} {
border-block-start: none !important;
}
} @else {
@include mixins.bordered-skin(".layout-nav-type-vertical.window-scrolled.layout-navbar-sticky #{$header}", "border-bottom");
}
// Footer
// -- Vertical
@include mixins.bordered-skin(".layout-nav-type-vertical.layout-footer-sticky .layout-footer .footer-content-container");
.layout-nav-type-vertical.layout-footer-sticky .layout-footer .footer-content-container {
border-block-end: none;
}
// -- Horizontal
@include mixins.bordered-skin(".layout-nav-type-horizontal.layout-footer-sticky .layout-footer");
.layout-nav-type-horizontal.layout-footer-sticky .layout-footer {
border-block-end: none;
}
/*
Missing components:
- Stepper
*/
.v-theme--light {
--skin-theme-background:
#{utils.map-deep-get(
variables.$css-vars,
"bordered",
"--v-theme-background",
"light"
)};
--skin-theme-surface:
#{utils.map-deep-get(
variables.$css-vars,
"bordered",
"--v-theme-surface",
"light"
)};
}
.v-theme--dark {
--skin-theme-background:
#{utils.map-deep-get(
variables.$css-vars,
"bordered",
"--v-theme-background",
"dark"
)};
--skin-theme-surface:
#{utils.map-deep-get(
variables.$css-vars,
"bordered",
"--v-theme-surface",
"dark"
)};
}
// Vertical Nav
.layout-vertical-nav {
border-inline-end: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
}
}

View File

@ -1 +0,0 @@
@use "bordered";

View File

@ -1,108 +0,0 @@
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
@use "@configured-variables" as variables;
// 👉 Expansion panels
.v-expansion-panel-title,
.v-expansion-panel-title--active,
.v-expansion-panel-title:hover,
.v-expansion-panel-title:focus,
.v-expansion-panel-title:focus-visible,
.v-expansion-panel-title--active:focus,
.v-expansion-panel-title--active:hover {
.v-expansion-panel-title__overlay {
opacity: 0 !important;
}
}
.v-expansion-panels {
:first-child {
border-start-end-radius: variables.$expansion-panel-border-radius-custom;
border-start-start-radius: variables.$expansion-panel-border-radius-custom;
}
:last-child {
border-end-end-radius: variables.$expansion-panel-border-radius-custom;
border-end-start-radius: variables.$expansion-panel-border-radius-custom;
}
}
// 👉 Set Elevation when panel open
.v-expansion-panels:not(.v-expansion-panels--variant-accordion) {
.v-expansion-panel.v-expansion-panel--active {
.v-expansion-panel__shadow {
@include mixins_elevation.elevation(3);
}
}
}
// v-tab with pill support
.v-tabs:not(.v-tabs-pill) {
&.v-tabs--vertical {
border-inline-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
}
.v-tab__slider {
inset-inline-end: 0;
inset-inline-start: unset;
}
.v-tabs.v-tabs-pill:not(.v-tabs--stacked) {
&.v-tabs--density-default {
--v-tabs-height: 38px;
}
.v-tab.v-btn {
border-radius: 0.5rem !important;
}
}
// 👉 added box shadow
.v-timeline-item {
.v-timeline-divider__dot {
.v-timeline-divider__inner-dot {
box-shadow: 0 0 0 0.1875rem rgb(var(--v-theme-on-surface-variant));
@each $color-name in variables.$theme-colors-name {
&.bg-#{$color-name} {
box-shadow: 0 0 0 0.1875rem rgba(var(--v-theme-#{$color-name}), 0.12);
}
}
}
}
}
// 👉 Timeline Outlined style
.v-timeline-variant-outlined.v-timeline {
.v-timeline-divider__dot {
.v-timeline-divider__inner-dot {
box-shadow: inset 0 0 0 0.125rem rgb(var(--v-theme-on-surface-variant));
@each $color-name in variables.$theme-colors-name {
background-color: rgb(var(--v-theme-surface)) !important;
&.bg-#{$color-name} {
box-shadow: inset 0 0 0 0.125rem rgb(var(--v-theme-#{$color-name}));
}
}
}
}
}
// 👉 Slider
.v-slider-thumb {
.v-slider-thumb__label {
background-color: variables.$slider-thumb-label-color;
color: rgb(var(--v-theme-on-primary));
}
.v-slider-thumb__label::before {
color: variables.$slider-thumb-label-color;
}
}
// 👉 switch inactive thumb style
.v-switch__thumb {
color: variables.$switch-thumb-inactive-color;
}

View File

@ -1,13 +0,0 @@
.layout-horizontal-nav {
.nav-group {
.nav-group-arrow {
font-size: 1.5rem;
}
&:not(.active) {
.nav-group-arrow {
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
}
}
}
}

View File

@ -1,11 +0,0 @@
@use "@configured-variables" as variables;
.bg-card {
background: rgb(var(--v-theme-surface)) !important;
}
.table-header-bg {
th {
background-color: rgb(var(--v-theme-grey-200));
}
}

View File

@ -1,41 +0,0 @@
@use "sass:string";
/*
This function is helpful when we have multi dimensional value
Assume we have padding variable `$nav-padding-horizontal: 10px;`
With above variable let's say we use it in some style:
```scss
.selector {
margin-left: $nav-padding-horizontal;
}
```
Now, problem is we can also have value as `$nav-padding-horizontal: 10px 15px;`
In this case above style will be invalid.
This function will extract the left most value from the variable value.
$nav-padding-horizontal: 10px; => 10px;
$nav-padding-horizontal: 10px 15px; => 10px;
This is safe:
```scss
.selector {
margin-left: get-first-value($nav-padding-horizontal);
}
```
*/
@function get-first-value($var) {
$start-at: string.index(#{$var}, " ");
@if $start-at {
@return string.slice(
#{$var},
0,
$start-at
);
} @else {
@return $var;
}
}

View File

@ -1,74 +0,0 @@
@use "sass:map";
@use "utils";
$vertical-nav-horizontal-padding-margin-custom: 1.75rem;
// We created this SCSS var to extract the start padding
// Docs: https://sass-lang.com/documentation/modules/string
// $vertical-nav-horizontal-padding => 0 8px;
// string.index(#{$vertical-nav-horizontal-padding}, " ") + 1 => 2
// string.index(#{$vertical-nav-horizontal-padding}, " ") => 1
// string.slice(0 8px, 2, -1) => 8px => $card-actions-padding-x
$vertical-nav-horizontal-padding-start: utils.get-first-value($vertical-nav-horizontal-padding-margin-custom) !default;
@forward "@/plugins/vuetify/@core/scss/base/variables" with(
$css-vars: (
/*
- Skins
- CSS var
- Theme
*/
"default": (
"--v-theme-background": (
"light": (247,247,249),
// "dark": (40,42,66),
"dark": (22, 29, 49)
),
"--v-theme-surface": (
"light": (255, 255, 255),
"dark": (40,51,78),
// "dark": (48,51,78),
// "dark": #283046
),
),
"bordered": (
"--v-theme-background": (
"light": (255 ,255, 255),
"dark": (40,42,66),
),
"--v-theme-surface": (
"light": (255, 255, 255),
"dark": (40,42,66),
),
),
) !default,
);
// 👉 Vertical nav
// This is used by nav items & nav header
$vertical-nav-horizontal-spacing: 0.75rem !default;
$vertical-nav-header-inline-spacing: $vertical-nav-horizontal-spacing 0.25rem !default;
$vertical-nav-horizontal-padding: 1rem 0.75rem !default;
// Section title margin bottom
$vertical-nav-section-title-mb: 0.75rem !default;
// Vertical nav header padding
$vertical-nav-header-padding: $vertical-nav-horizontal-padding !default;
$vertical-nav-items-nested-icon-size: 0.5rem !default;
// 👉 expansion panel
$expansion-panel-border-radius-custom: 8px !default;
// 👉 range-slider
$slider-thumb-label-color: rgb(117, 117, 117) !default;
// 👉 switch
$switch-thumb-inactive-color: rgb(250, 250, 250) !default;
// 👉 Horizontal nav
// Horizontal nav icons
$horizontal-nav-third-level-icon-size: 0.5rem !default;
$horizontal-nav-items-icon-margin-inline-end: 0.75rem !default;

View File

@ -1,32 +0,0 @@
@use "@configured-variables" as variables;
$divider-gap: 0.625rem;
.layout-vertical-nav {
.nav-section-title {
.title-text {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
column-gap: $divider-gap;
&::before {
flex: 0 1 calc(variables.$vertical-nav-horizontal-padding-start - $divider-gap);
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
content: "";
margin-inline-start: -#{variables.$vertical-nav-horizontal-padding-start};
}
}
}
// nested level nav icon
.nav-group {
.nav-group,
.nav-link :not(.router-link-active) {
.nav-item-icon {
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
}
}
}
}

View File

@ -1,12 +0,0 @@
@use "sass:map";
@forward "@/plugins/vuetify/@core/scss/base";
// Layout
@use "vertical-nav";
@use "horizontal-nav";
// Components
@use "components";
// Utilities
@use "utilities";

View File

@ -1,95 +0,0 @@
@use "@styles/variables/_vuetify.scss" as vuetify;
@use "vuetify/lib/styles/tools/_elevation" as mixins_elevation;
@use "@layouts/styles/mixins" as layoutsMixins;
.apexcharts-canvas {
&line[stroke="transparent"] {
display: "none";
}
.apexcharts-tooltip {
@include mixins_elevation.elevation(3);
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
background: rgb(var(--v-theme-surface));
.apexcharts-tooltip-title {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
background: rgb(var(--v-theme-surface));
font-weight: 600;
}
&.apexcharts-theme-light {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
&.apexcharts-theme-dark {
color: white;
}
.apexcharts-tooltip-series-group:first-of-type {
padding-block-end: 0;
}
}
.apexcharts-xaxistooltip {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
background: rgb(var(--v-theme-grey-50));
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
&::after {
border-block-end-color: rgb(var(--v-theme-grey-50));
}
&::before {
border-block-end-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
}
.apexcharts-yaxistooltip {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
background: rgb(var(--v-theme-grey-50));
&::after {
border-inline-start-color: rgb(var(--v-theme-grey-50));
}
&::before {
border-inline-start-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
}
.apexcharts-xaxistooltip-text,
.apexcharts-yaxistooltip-text {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.apexcharts-yaxis .apexcharts-yaxis-texts-g .apexcharts-yaxis-label {
@include layoutsMixins.rtl {
text-anchor: start;
}
}
.apexcharts-text,
.apexcharts-tooltip-text,
.apexcharts-datalabel-label,
.apexcharts-datalabel,
.apexcharts-xaxistooltip-text,
.apexcharts-yaxistooltip-text,
.apexcharts-legend-text {
font-family: vuetify.$body-font-family !important;
}
.apexcharts-pie-label {
fill: white;
filter: none;
}
.apexcharts-marker {
box-shadow: none;
}
.apexcharts-legend-marker {
margin-inline-end: 0.3875rem !important;
}
}

View File

@ -1,254 +0,0 @@
@use "vuetify/lib/styles/tools/elevation" as elevation;
.fc {
--fc-today-bg-color: rgba(var(--v-theme-on-surface), 0.04);
--fc-border-color: rgba(var(--v-border-color), var(--v-border-opacity));
--fc-neutral-bg-color: rgb(var(--v-theme-background));
--fc-list-event-hover-bg-color: rgba(var(--v-theme-on-surface), 0.02);
--fc-page-bg-color: rgb(var(--v-theme-surface));
--fc-event-border-color: currentcolor;
a {
color: inherit;
}
.fc-timegrid-divider {
padding: 0;
}
.fc-col-header-cell-cushion {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
font-size: 0.875rem;
font-weight: 600;
}
.fc-toolbar .fc-toolbar-title {
margin-inline-start: 0.25rem;
}
.fc-toolbar.fc-header-toolbar {
margin-block-end: 1rem;
}
.fc-event-time {
font-size: 0.75rem;
}
.fc-timegrid-event {
.fc-event-title {
font-size: 0.875rem;
}
}
.fc-prev-button {
padding-inline-start: 0;
}
.fc-prev-button,
.fc-next-button {
padding: 0.25rem;
}
.fc-col-header .fc-col-header-cell .fc-col-header-cell-cushion {
padding: 0.5rem;
text-decoration: none !important;
}
.fc-timegrid .fc-timegrid-slots .fc-timegrid-slot {
block-size: 3rem;
}
// Removed double border on left in list view
.fc-list {
border-inline-start-color: transparent;
font-size: 0.875rem;
.fc-list-day-cushion.fc-cell-shaded {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
font-weight: 600;
}
.fc-list-event-time,
.fc-list-event-title {
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
}
.fc-list-day .fc-list-day-text,
.fc-list-day .fc-list-day-side-text {
text-decoration: none;
}
}
.fc-timegrid-axis {
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
font-size: 0.75rem;
text-transform: capitalize;
}
.fc-timegrid-slot-label-frame {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
font-size: 0.75rem;
text-align: center;
text-transform: uppercase;
}
.fc-header-toolbar {
flex-wrap: wrap;
column-gap: 0.5rem;
margin-block: 1rem;
margin-inline: 1rem 1.25rem;
row-gap: 1rem;
}
.fc-toolbar-chunk {
display: flex;
align-items: center;
.fc-button-group {
.fc-button-primary {
&,
&:hover,
&:not(.disabled):active {
border-color: transparent;
background-color: transparent;
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
}
&:focus {
box-shadow: none !important;
}
}
}
&:last-child {
.fc-button-group {
border: 0.0625rem solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 0.375rem;
.fc-button {
font-size: 0.9rem;
letter-spacing: 0.0187rem;
padding-inline: 1rem;
text-transform: uppercase;
&:not(:last-child) {
border-inline-end: 0.0625rem solid rgba(var(--v-border-color), var(--v-border-opacity));
}
&.fc-button-active {
background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity));
color: rgb(var(--v-theme-primary));
}
}
}
}
}
.fc-toolbar-title {
display: inline-block;
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
font-size: 1.25rem;
font-weight: 500;
}
.fc-scrollgrid-section {
th {
border-inline-end-color: transparent;
}
}
// Calendar content container
.fc-view-harness {
min-block-size: 40.625rem;
}
.fc-event {
border-color: transparent;
margin-block-end: 0.3rem;
padding-block: 0.1875rem;
padding-inline: 0.3125rem;
}
.fc-event-main {
color: inherit;
font-size: 0.75rem;
padding-inline: 0.25rem;
}
tbody[role="rowgroup"] {
> tr > td[role="presentation"] {
border: none;
}
}
.fc-scrollgrid {
border-inline-start: none;
}
.fc-daygrid-day {
padding: 0.3125rem;
}
.fc-daygrid-day-number {
padding-block: 0.5rem;
padding-inline: 0.75rem;
}
.fc-list-event-dot {
color: inherit;
--fc-event-border-color: currentcolor;
}
.fc-list-event {
background-color: transparent !important;
}
.fc-popover {
@include elevation.elevation(3);
border-radius: 6px;
.fc-popover-header,
.fc-popover-body {
padding: 0.5rem;
}
.fc-popover-title {
margin: 0;
font-size: 1rem;
font-weight: 500;
}
}
// 👉 sidebar toggler
.fc-toolbar-chunk {
.fc-button-group {
align-items: center;
.fc-button .fc-icon {
vertical-align: bottom;
}
// Below two `background-image` styles contains static color due to browser limitation of not parsing the css var inside CSS url()
.fc-drawerToggler-button {
display: none;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' stroke='rgba(94,86,105,0.68)' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1'%3E%3Cpath d='M3 12h18M3 6h18M3 18h18'/%3E%3C/svg%3E");
background-position: 50%;
background-repeat: no-repeat;
block-size: 1.5625rem;
font-size: 0;
inline-size: 1.5625rem;
margin-inline-end: 0.25rem;
@media (max-width: 1264px) {
display: block !important;
}
.v-theme--dark & {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' stroke='rgba(232,232,241,0.68)' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1'%3E%3Cpath d='M3 12h18M3 6h18M3 18h18'/%3E%3C/svg%3E");
}
}
}
}
}

View File

@ -1,43 +0,0 @@
// 👉 Avatar
.v-avatar {
font-size: 1.125rem;
}
$alert-icon-size: 22px;
$alert-prominent-icon-size: 38px;
// 👉 Alert
.v-alert {
&:not(.v-alert--prominent) {
.v-icon {
block-size: $alert-icon-size !important;
font-size: $alert-icon-size !important;
inline-size: $alert-icon-size !important;
}
}
&.v-alert--prominent {
.v-icon {
block-size: $alert-prominent-icon-size !important;
font-size: $alert-prominent-icon-size !important;
inline-size: $alert-prominent-icon-size !important;
}
}
}
// 👉 Table
.v-table {
th {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
font-weight: 500;
}
}
// // 👉 Timeline
.v-timeline {
.v-timeline-item:not(:last-child) {
.v-timeline-item__body {
margin-block-end: 0.625rem;
}
}
}

View File

@ -1,225 +0,0 @@
$shadow-key-umbra-opacity-custom: var(--v-shadow-key-umbra-opacity);
$shadow-key-penumbra-opacity-custom: var(--v-shadow-key-penumbra-opacity);
$shadow-key-ambient-opacity-custom: var(--v-shadow-key-ambient-opacity);
$font-family-custom: "Inter", sans-serif, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
@forward "../../../base/libs/vuetify/variables" with (
// 👉 font-family
$body-font-family: $font-family-custom !default,
$border-radius-root: 8px !default,
$shadow-key-umbra: (
0: (0 0 0 0 var(--v-shadow-key-umbra-opacity)),
1: (0 1px 2px -1px var(--v-shadow-key-umbra-opacity)),
2: (0 1px 3px -1px var(--v-shadow-key-umbra-opacity)),
3: (0 1px 6px -1px var(--v-shadow-key-umbra-opacity)),
4: (0 1px 7px -2px var(--v-shadow-key-umbra-opacity)),
5: (0 2px 8px -2px var(--v-shadow-key-umbra-opacity)),
6: (0 2px 9px -2px var(--v-shadow-key-umbra-opacity)),
7: (0 2px 10px -3px var(--v-shadow-key-umbra-opacity)),
8: (0 3px 11px -3px var(--v-shadow-key-umbra-opacity)),
9: (0 4px 12px -3px var(--v-shadow-key-umbra-opacity)),
10: (0 5px 13px -4px var(--v-shadow-key-umbra-opacity)),
11: (0 6px 14px -4px var(--v-shadow-key-umbra-opacity)),
12: (0 6px 15px -4px var(--v-shadow-key-umbra-opacity)),
13: (0 7px 14px -5px var(--v-shadow-key-umbra-opacity)),
14: (0 6px 17px -5px var(--v-shadow-key-umbra-opacity)),
15: (0 7px 18px -5px var(--v-shadow-key-umbra-opacity)),
16: (0 7px 19px -6px var(--v-shadow-key-umbra-opacity)),
17: (0 7px 20px -6px var(--v-shadow-key-umbra-opacity)),
18: (0 8px 21px -6px var(--v-shadow-key-umbra-opacity)),
19: (0 8px 22px -7px var(--v-shadow-key-umbra-opacity)),
20: (0 9px 23px -7px var(--v-shadow-key-umbra-opacity)),
21: (0 9px 24px -7px var(--v-shadow-key-umbra-opacity)),
22: (0 9px 25px -8px var(--v-shadow-key-umbra-opacity)),
23: (0 10px 26px -8px var(--v-shadow-key-umbra-opacity)),
24: (0 10px 27px -8px var(--v-shadow-key-umbra-opacity))
) !default,
$shadow-key-penumbra: (
0: (0 0 0 0 $shadow-key-penumbra-opacity-custom),
1: (0 1px 2px 1px $shadow-key-penumbra-opacity-custom),
2: (0 2px 3px 1px $shadow-key-penumbra-opacity-custom),
3: (0 2px 4px 1px $shadow-key-penumbra-opacity-custom),
4: (0 3px 5px 1px $shadow-key-penumbra-opacity-custom),
5: (0 3px 6px 1px $shadow-key-penumbra-opacity-custom),
6: (0 4px 7px 1px $shadow-key-penumbra-opacity-custom),
7: (0 4px 8px 1px $shadow-key-penumbra-opacity-custom),
8: (0 6px 9px 1px $shadow-key-penumbra-opacity-custom),
9: (0 5px 10px 1px $shadow-key-penumbra-opacity-custom),
10: (0 6px 12px 3px $shadow-key-penumbra-opacity-custom),
11: (0 8px 12px 1px $shadow-key-penumbra-opacity-custom),
12: (0 10px 13px 2px $shadow-key-penumbra-opacity-custom),
13: (0 12px 14px 2px $shadow-key-penumbra-opacity-custom),
14: (0 12px 15px 2px $shadow-key-penumbra-opacity-custom),
15: (0 14px 16px 2px $shadow-key-penumbra-opacity-custom),
16: (0 15px 17px 2px $shadow-key-penumbra-opacity-custom),
17: (0 16px 18px 2px $shadow-key-penumbra-opacity-custom),
18: (0 17px 19px 2px $shadow-key-penumbra-opacity-custom),
19: (0 18px 20px 2px $shadow-key-penumbra-opacity-custom),
20: (0 18px 21px 3px $shadow-key-penumbra-opacity-custom),
21: (0 18px 22px 3px $shadow-key-penumbra-opacity-custom),
22: (0 20px 23px 3px $shadow-key-penumbra-opacity-custom),
23: (0 22px 24px 3px $shadow-key-penumbra-opacity-custom),
24: (0 22px 25px 3px $shadow-key-penumbra-opacity-custom)
) !default,
$shadow-key-ambient: (
0: (0 0 0 0 $shadow-key-ambient-opacity-custom),
1: (0 1px 2px 2px $shadow-key-ambient-opacity-custom),
2: (0 1px 3px 2px $shadow-key-ambient-opacity-custom),
3: (0 1px 4px 2px $shadow-key-ambient-opacity-custom),
4: (0 1px 4px 2px $shadow-key-ambient-opacity-custom),
5: (0 1px 5px 4px $shadow-key-ambient-opacity-custom),
6: (0 2px 6px 4px $shadow-key-ambient-opacity-custom),
7: (0 2px 7px 4px $shadow-key-ambient-opacity-custom),
8: (0 3px 8px 4px $shadow-key-ambient-opacity-custom),
9: (0 4px 9px 5px $shadow-key-ambient-opacity-custom),
10: (0 5px 10px 5px $shadow-key-ambient-opacity-custom),
11: (0 6px 11px 5px $shadow-key-ambient-opacity-custom),
12: (0 5px 12px 5px $shadow-key-ambient-opacity-custom),
13: (0 5px 14px 6px $shadow-key-ambient-opacity-custom),
14: (0 5px 14px 6px $shadow-key-ambient-opacity-custom),
15: (0 5px 15px 6px $shadow-key-ambient-opacity-custom),
16: (0 5px 16px 6px $shadow-key-ambient-opacity-custom),
17: (0 5px 17px 7px $shadow-key-ambient-opacity-custom),
18: (0 6px 18px 7px $shadow-key-ambient-opacity-custom),
19: (0 6px 19px 7px $shadow-key-ambient-opacity-custom),
20: (0 7px 20px 7px $shadow-key-ambient-opacity-custom),
21: (0 7px 21px 7px $shadow-key-ambient-opacity-custom),
22: (0 7px 22px 7px $shadow-key-ambient-opacity-custom),
23: (0 8px 23px 7px $shadow-key-ambient-opacity-custom),
24: (0 8px 24px 7px $shadow-key-ambient-opacity-custom)
) !default,
// 👉 typography
$typography: (
"h1": (
"weight": 500,
"line-height": 7rem,
"letter-spacing": -0.0938rem,
),
"h2": (
"weight": 500,
"line-height": 4.5rem,
"letter-spacing": -0.0313rem,
),
"h3": (
"weight": 500,
"line-height": 3.5rem,
),
"h4": (
"weight": 500,
"letter-spacing": 0.0156rem,
),
"h5": (
"weight": 500,
),
"h6": (
"letter-spacing": 0.0094rem,
),
"subtitle-1": (
"letter-spacing": 0.0094rem,
),
"subtitle-2": (
"line-height": 1.3125rem,
"letter-spacing": 0.0063rem,
),
"body-1": (
"letter-spacing": 0.0094rem,
),
"body-2": (
"line-height": 1.3125rem,
"letter-spacing": 0.0094rem,
),
"caption": (
"line-height": 0.875rem,
"letter-spacing": 0.025rem,
),
"button": (
"line-height": 1.5rem,
"letter-spacing": 0.025rem,
),
"overline": (
"weight": 400,
"line-height": 0.875rem,
"letter-spacing": 0.0625rem,
),
) !default,
// 👉 alert
$alert-padding: 17.5px !default,
$alert-title-font-size: 16px !default,
$alert-prepend-margin-inline-end: 13px !default,
$alert-background: rgb(var(--v-theme-alert-background)) !default,
// 👉 buttons
$button-height: 38px,
$button-line-height: 24px,
$button-padding-ratio: 1.8,
// 👉 card
$card-border-radius: 10px !default,
// 👉 chips
$chip-font-size: 13px !default,
$chip-close-size: 22px !default,
// 👉 dialogs
$dialog-card-header-padding: 20px 20px 0 !default,
$dialog-card-text-padding: 20px 20px !default,
$dialog-card-header-text-padding-top: 20px !default,
$dialog-border-radius: 10px !default,
// 👉 expansion panel
$expansion-panel-border-radius: 0 !default,
$expansion-panel-active-title-min-height: 50px !default,
$expansion-panel-title-min-height: 50px !default,
$expansion-panel-title-padding: 16px 20px !default,
$expansion-panel-text-padding: 8px 20px 16px !default,
// 👉 list item
$list-item-padding:12px 16px !default,
$list-item-icon-margin-end: 14px !default,
$list-nav-padding: 16px !default,
$rounded: (
"shaped": 24px 0,
) !default,
// 👉 overlay
$overlay-opacity: 50% !default,
// 👉 pagination
$pagination-item-margin: 3px !default,
// 👉 snackbar
$snackbar-content-padding: 6px 16px,
$snackbar-background: rgb(var(--v-theme-snackbar-background)),
$snackbar-color: rgb(var(--v-theme-on-snackbar-background)),
// 👉 tooltip
$tooltip-padding: 4px 8px !default,
$tooltip-background-color: rgba(var(--v-theme-tooltip-background), 0.9) !default,
$tooltip-border-radius: 6px !default,
$tooltip-font-size: 0.6875rem !default,
$tooltip-line-height: 16px !default,
// 👉 Timeline
$timeline-dot-divider-background: transparent !default,
$timeline-item-padding: 16px !default,
// 👉 Table
$table-header-height: 54px !default,
$table-row-height: 50px !default,
// 👉 range slider
$slider-track-active-size: 4px !default,
$slider-thumb-label-padding: 4px 12px !default,
$slider-thumb-label-font-size: 14px !default,
$slider-thumb-label-height: 29px !default,
);

View File

@ -1,2 +0,0 @@
@use "@/plugins/vuetify/@core/scss/base/libs/vuetify";
@use "overrides";

View File

@ -1,35 +0,0 @@
.layout-blank {
.misc-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.25rem;
min-block-size: calc(var(--vh, 1vh) * 100);
}
.misc-avatar {
z-index: 1;
}
}
.footer-coming-soon {
position: absolute;
z-index: -1;
inline-size: 100%;
inset-block-end: 0;
inset-inline-start: 0;
}
.footer-coming-soon-obj {
position: absolute;
inline-size: 100%;
inset-block-end: 12%;
inset-inline-start: 15%;
}
@media (max-width: 1200px) {
.footer-coming-soon-obj {
inset-block-end: 5%;
}
}

View File

@ -1,45 +0,0 @@
.layout-blank {
.auth-wrapper {
min-block-size: calc(var(--vh, 1vh) * 100);
}
.auth-card {
z-index: 1 !important;
}
}
@media (min-width: 960px) {
.skin--bordered {
.auth-card-v2 {
border-inline-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
}
}
@media (max-width: 1200px) {
.auth-footer-mask {
inset-block-end: 12% !important;
}
}
.auth-logo {
position: absolute;
z-index: 1;
inset-block-start: 1.8rem;
inset-inline-start: 2.5rem;
}
.auth-card-v2 {
z-index: 1;
}
.auth-illustration {
z-index: 1;
max-inline-size: 48rem;
}
.auth-footer-mask {
position: absolute;
inline-size: 100%;
inset-block-end: 7%;
}

View File

@ -1,2 +0,0 @@
@forward "vertical-nav";
@forward "nav";

Some files were not shown because too many files have changed in this diff Show More