Initial commit

This commit is contained in:
corrad-software 2024-08-26 09:09:46 +08:00 committed by GitHub
commit 46b84b1535
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
330 changed files with 24142 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env
dist
sw.*

0
.nuxtignore Normal file
View File

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# Nuxt 3 Minimal Starter
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# yarn
yarn install
# npm
npm install
# pnpm
pnpm install --shamefully-hoist
```
## Development Server
Start the development server on http://localhost:3000
```bash
npm run dev
```
## Production
Build the application for production:
```bash
npm run build
```
Locally preview production build:
```bash
npm run preview
```
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
# corrad

9
app.config.js Normal file
View File

@ -0,0 +1,9 @@
// app.config.ts
export default defineAppConfig({
nuxtIcon: {
size: "24px", // default <Icon> size applied
aliases: {
nuxt: "logos:nuxt-icon",
},
},
});

34
app.vue Normal file
View File

@ -0,0 +1,34 @@
<script setup>
useHead({
title: "corrad | Innovative solutions for captivating content",
description: "Home page",
htmlAttrs: {
lang: "en",
},
});
const nuxtApp = useNuxtApp();
const loading = ref(true);
onMounted(() => {
// Hide loading indicator if not hydrating
setTimeout(() => {
loading.value = false;
}, 1000);
// Get theme from localStorage
let theme = localStorage.getItem("theme") || "default";
document.documentElement.setAttribute("data-theme", theme);
});
</script>
<template>
<div>
<VitePwaManifest />
<NuxtLoadingIndicator />
<NuxtLayout>
<Loading v-if="loading" />
<NuxtPage :key="$route.fullPath" v-else />
</NuxtLayout>
</div>
</template>

1
assets/img/avatar/1.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" fill="none"><metadata><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>Adventurer Neutral</dc:title><dc:creator><cc:Agent><dc:title>Lisa Wischofsky</dc:title></cc:Agent></dc:creator><dc:source>https://www.instagram.com/lischi_art/</dc:source><cc:license rdf:resource="https://creativecommons.org/licenses/by/4.0/"/></cc:Work><cc:License rdf:about="https://creativecommons.org/licenses/by/4.0/"><cc:permits rdf:resource="https://creativecommons.org/ns#Reproduction"/><cc:permits rdf:resource="https://creativecommons.org/ns#Distribution"/><cc:permits rdf:resource="https://creativecommons.org/ns#DerivativeWorks"/><cc:requires rdf:resource="https://creativecommons.org/ns#Notice"/><cc:requires rdf:resource="https://creativecommons.org/ns#Attribution"/></cc:License></rdf:RDF></metadata><mask id="avatarsRadiusMask"><rect width="400" height="400" rx="0" ry="0" x="0" y="0" fill="#fff"/></mask><g mask="url(#avatarsRadiusMask)"><rect fill="rgba(242, 211, 177, 1)" width="400" height="400" x="0" y="0"/><g transform="translate(-279 -322)"><path d="M617.23 468.82c1.16 1.34 2.37 2.36 2.84 4.14 4.46 17.23 1.58 36.09-7.98 51.11-8.01 12.77-20.52 22.56-34.83 27.28-11.68 3.74-24.33 4.31-36.24 1.27-17.06-4.16-32.11-15.59-40.77-30.86-7.18-12.23-9.85-26.73-8.06-40.75.53-3.41 1.75-7.26 5.57-8.09 8.61-1.53 17.56-1.34 26.27-2.27 24.31-1.49 48.65-2.37 73.01-2.08 6.75-.37 13.45.4 20.19.25Z" fill="#000"/><path d="M607.09 474.28c2.48.12 4.97.24 7.45.37 3.8 14.15 1.78 29.85-5.17 42.73-6.79 12.81-18.41 23-32.1 27.84-14.68 5.57-31.38 4.73-45.56-1.92-13.38-6.21-24.24-17.42-29.82-31.09-4.19-10.23-5.98-22.11-3.62-32.99 1.86-.72 3.69-1.32 5.7-1.4 16.9-1.37 33.84-2.38 50.78-3.11-.41 5.34-.18 10.53 2.09 15.47 3.42 7.65 10.45 13.28 18.62 15.04 4.89.87 9.94.6 14.61-1.13 8.19-2.81 14.36-9.94 16.48-18.26.92-3.9.66-7.59.54-11.55Z" fill="#fff"/><path d="M432.03 480.67c2.01-.23 3.79.3 5.74.68 4.2 9.05 4.82 19.93 3.38 29.69-2.48 14.77-11.15 28.19-23.63 36.48-10.93 7.31-24.46 10.57-37.51 8.79-15.32-1.77-29.51-10.46-38.25-23.14-6.82-9.71-10.02-21.43-10.01-33.24 6.53-3.27 13.18-3.72 20.49-5.47 20.5-4.17 41.07-8.1 61.75-11.22 6-.99 11.97-2.04 18.04-2.57Z" fill="#000"/><path d="M433.71 486.06c1.44 5.27 2.74 10.43 2.6 15.94-.2 9.49-3.01 19.08-8.31 26.99-9.89 15.07-28.07 23.78-46.01 21.88-12.38-1.34-24.01-7.28-32.17-16.71-7.5-8.47-11.57-19.33-12.24-30.58 14.45-3.54 29.14-6.22 43.76-9.01 1.32 6.49 4.55 12.28 10.11 16.03 9.04 6.32 21.92 5.38 29.9-2.26 6.12-5.72 8.04-13.57 7.2-21.73 1.72-.19 3.44-.38 5.16-.55Z" fill="#fff"/></g><g transform="translate(-279 -322)"><g fill="#000"><path d="M501.99 384.96c9.91.12 19.87 3.23 28.83 7.31 3.36 1.88 7.05 4.01 7.31 8.29-5.14-.86-10.14-2.06-15.38-2.28-11.6-.68-23.13 1.36-33.25 7.23-2.54 1.41-4.95 3.26-7.64 4.35-1.8.27-3.92-1.99-3.94-3.73-.18-5.81 1.95-11.92 6.53-15.7 5.03-4.23 11.15-5.3 17.54-5.47ZM414.03 402.61c5.81.15 10.93 5.3 12.51 10.64.8 2.63-.46 5.72-3.53 5.61-8.82.21-18.21.03-26.81 2.26-7.52 2.01-14.74 6.71-21.36 10.74-1.22.81-2.57.96-3.98 1.22.33-1.93.64-3.66 1.94-5.21 3.28-4.33 6.78-8.58 10.74-12.3 5.88-5.17 13.01-8.96 20.32-11.66 3.23-1.11 6.76-1.58 10.17-1.3Z"/></g></g><g transform="translate(-279 -322)"><path d="M517.9 557.82c1.53.3 2.76 1.6 2.73 3.2-7.1 16.81-15.06 33.2-25.17 48.42-4.62 6.97-9.13 14.14-14.43 20.61-2.18 3.27-7.96 2.92-10.79.72-6.93-4.92-13.33-10.65-19.13-16.87-9.22-10.37-17.94-20.12-24.71-32.38-.81-1.89-2.3-3.93-.75-5.87.54-1.24 2.97-.86 3.53.16 5.13 9.16 11.47 17.71 18.52 25.49 6.97 8.11 14.24 15.76 22.74 22.29 1.12.81 2.37 1.74 3.71 2.1 2.61.27 3.89-1.32 5.32-3.19 13.52-19.03 25.72-38.76 34.68-60.38.81-2.01 1.46-3.68 3.75-4.3Z" fill="#000"/></g><g transform="translate(-279 -322)"/></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

1
assets/img/avatar/2.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" fill="none"><metadata><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>Adventurer Neutral</dc:title><dc:creator><cc:Agent><dc:title>Lisa Wischofsky</dc:title></cc:Agent></dc:creator><dc:source>https://www.instagram.com/lischi_art/</dc:source><cc:license rdf:resource="https://creativecommons.org/licenses/by/4.0/"/></cc:Work><cc:License rdf:about="https://creativecommons.org/licenses/by/4.0/"><cc:permits rdf:resource="https://creativecommons.org/ns#Reproduction"/><cc:permits rdf:resource="https://creativecommons.org/ns#Distribution"/><cc:permits rdf:resource="https://creativecommons.org/ns#DerivativeWorks"/><cc:requires rdf:resource="https://creativecommons.org/ns#Notice"/><cc:requires rdf:resource="https://creativecommons.org/ns#Attribution"/></cc:License></rdf:RDF></metadata><mask id="avatarsRadiusMask"><rect width="400" height="400" rx="0" ry="0" x="0" y="0" fill="#fff"/></mask><g mask="url(#avatarsRadiusMask)"><rect fill="rgba(242, 211, 177, 1)" width="400" height="400" x="0" y="0"/><g transform="translate(-279 -322)"><path d="M506.03 461.12c5.49-1.16 11.66 2.44 16.92 3.98 22.15 7.08 45.9 9.46 69.06 7.74 6.23-.6 12.8-2.41 19.04-2.01 6.2.66 10.39 6.07 10.95 12.06.76 10.8-.8 22-5.08 31.99-5.94 13.99-16.89 25.72-30.44 32.6-21.35 11.08-48.35 9.02-67.75-5.21-14.69-10.49-24.7-27.34-26.67-45.3-.85-8.69-.38-17.4 2.09-25.8 1.39-5.24 6.78-9 11.88-10.05ZM428.25 466.75c3.63 2.23 5.82 6.6 7.78 10.25 5.54 10.79 7 23.18 4.95 35.07-2.05 10.58-7.16 20.51-14.73 28.2-8.25 8.47-19.31 14.15-31.01 15.88-14.37 2.22-29.54-1.48-41.16-10.25-11.27-8.31-19.08-21.07-21.34-34.89-.57-3.68-1.52-7.6.15-11.13 1.65-4.19 5.67-7.09 10.11-7.6 4.94-.59 9.87-1.18 14.78-2 18.38-3.3 35.26-11.25 50.36-22.12 5.88-5.11 13.54-5.46 20.11-1.41Z" fill="#000"/><path d="M513.41 467.65c12.21 5.03 25.51 7.97 38.57 9.71 15.64 2.02 31.39 2.59 47.04.31 3.87-.56 8.11-1.82 11.99-1.53 2.43.08 4.35 2.67 4.82 4.87 1.65 11.7-.21 23.76-5.45 34.37-6.36 13.22-17.88 24.01-31.6 29.27-10.66 4.29-22.51 5.42-33.76 3.03-17.11-3.39-32.41-14.68-40.59-30.1-6.9-12.96-9.37-29-5.07-43.17 1.67-6.19 8.01-9.93 14.05-6.76ZM427.77 474.23c4.78 7.05 7.55 15.26 8.39 23.72 1.24 16.39-6.13 32.97-19.25 42.91-8.79 6.83-19.83 10.35-30.92 10.3-11.61-.38-23-4.71-31.68-12.48-9.68-8.5-15.78-20.84-16.54-33.71-.47-3.19 2.13-6.46 5.26-6.95 3.24-.67 6.63-.78 9.92-1.18 18.5-2.26 36.59-9.6 52.19-19.69 3.09-1.92 5.86-4.31 8.97-6.18 4.4-2.3 10.95-1.13 13.66 3.26Z" fill="#fff"/><path d="M533.54 496.92c12.3-1.1 23.89 8.53 24.08 21.08.84 11.05-7.56 21.22-18.45 22.83-11.91 2.14-23.93-6.81-25.41-18.8-1.92-12.29 7.51-23.97 19.78-25.11ZM400.49 505.5c5.96-1.31 12.63.86 17.06 4.96 6.53 5.99 8.17 16.12 3.72 23.81-4.15 7.79-13.62 11.6-22.08 9.37-8.34-2.18-14.54-9.95-14.42-18.64-.3-9.44 6.57-17.6 15.72-19.5Z" fill="#000"/></g><g transform="translate(-279 -322)"><g fill="#000"><path d="M530.96 352.34c21.47-3.31 42.8 3.14 60.6 15.09 12.06 8.19 22.9 19.68 28.97 33.03 1.1 2.96-.3 3.73-2.51 5.27-3.95-2.26-6.87-5.58-10.47-8.29-12.99-9.78-28.03-16.38-43.78-20.25-14.93-3.51-30.56-5.05-45.8-2.8-4.78.41-9.28 1.92-13.97 2.67-2.41.2-5.1-1.21-4.21-4.04 1.9-4.15 4.23-8.5 7.6-11.65 6.04-5.62 15.64-7.85 23.57-9.03ZM383 428.83c13.38-.82 26.33-.61 39.03 4.23 5.85 2.37 11.52 5.91 13.93 12.05 1.38 4.31.91 8.87-1.73 12.61-4.9-.76-9.4-2.6-14.18-3.82-23.13-6.33-47.19-8.01-71.04-5.96-4.88.33-9.86 2.03-14.72 1.78-1.78-.48-1.17-2.2-.97-3.54.88-2.51 3.58-3.86 5.76-5.09 13.75-7.25 28.55-10.65 43.92-12.26Z"/></g></g><g transform="translate(-279 -322)"><path d="M579.989 568.69c2.42 1 1.92 3.4 1.38 5.45-6.78 13.35-16.08 25.18-28.35 33.9-17.19 12.49-38.88 18.4-60.02 17.58-26.81-.96-53.07-12.04-73.44-29.31-.4-2.42-.49-3.71 1.45-5.42 51.83-7.13 106.78-15.03 158.98-22.2Z" fill="#000"/><path d="M573.86 575.51c-8.17 15.05-20.98 27.46-36.37 34.99-12.46 6.12-26.6 9.44-40.49 9.19-24.71.18-49.01-9.01-68.58-23.87 47.93-6.59 97.73-13.8 145.44-20.31Z" fill="#8F2E45"/></g><g transform="translate(-279 -322)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

1
assets/img/avatar/3.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" fill="none"><metadata><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>Adventurer Neutral</dc:title><dc:creator><cc:Agent><dc:title>Lisa Wischofsky</dc:title></cc:Agent></dc:creator><dc:source>https://www.instagram.com/lischi_art/</dc:source><cc:license rdf:resource="https://creativecommons.org/licenses/by/4.0/"/></cc:Work><cc:License rdf:about="https://creativecommons.org/licenses/by/4.0/"><cc:permits rdf:resource="https://creativecommons.org/ns#Reproduction"/><cc:permits rdf:resource="https://creativecommons.org/ns#Distribution"/><cc:permits rdf:resource="https://creativecommons.org/ns#DerivativeWorks"/><cc:requires rdf:resource="https://creativecommons.org/ns#Notice"/><cc:requires rdf:resource="https://creativecommons.org/ns#Attribution"/></cc:License></rdf:RDF></metadata><mask id="avatarsRadiusMask"><rect width="400" height="400" rx="0" ry="0" x="0" y="0" fill="#fff"/></mask><g mask="url(#avatarsRadiusMask)"><rect fill="rgba(242, 211, 177, 1)" width="400" height="400" x="0" y="0"/><g transform="translate(-279 -322)"><path d="M540.491 447.73c18.9-2.42 39.64-.58 56.96 7.8 8.55 4.17 16.74 10.3 21.22 18.89 3.9 7.34 3.91 16.76-.16 24.03-4.22 8.05-12.01 14.1-19.93 18.24-13.55 7.02-29.41 9.83-44.57 9.65-12.04.02-23.93-1.81-35.21-6.11-8.97-3.47-17.65-8.68-23.74-16.26-4.84-6.12-7.63-14.13-5.89-21.93 1.84-8.56 7.49-15.36 14.39-20.47 10.65-7.84 23.96-11.98 36.93-13.84Z" fill="#000"/><path d="M546.98 452.71c8.4-.27 17.24-.58 25.5 1.08 10.59 1.7 21.45 5.3 30.3 11.45 5.49 3.95 10.43 9.09 12.35 15.73 2.02 6.81-.27 14.13-4.75 19.45-6.1 7.46-15.56 12.38-24.55 15.43-9.89 3.26-20.41 5.08-30.83 4.84-10.21.27-20.5-1.38-30.23-4.45-9.19-3.05-18.45-7.69-24.78-15.23-4.29-5.04-6.9-12.45-4.86-18.97 1.51-6.55 6.69-12.06 11.94-15.96 11.4-8.26 26.07-12.06 39.91-13.37Z" fill="#fff"/><path d="M373.48 460.76c16.32-2.29 34.23-.91 49.1 6.64 7.74 3.99 15.42 10.43 17.96 19.07 2.33 7.1-.17 14.74-4.65 20.42-6.28 7.76-15.27 12.43-24.64 15.41-14.57 4.5-30.41 5.01-45.29 1.76-9.88-2.25-20.02-6.47-27.16-13.89-4.71-4.75-7.92-11.31-7.35-18.12.54-7.22 4.98-13.65 10.43-18.18 8.81-7.42 20.37-11.3 31.6-13.11Z" fill="#000"/><path d="M371.5 466.92c15.62-3 32.92-1.77 47.43 5.05 6.36 3.02 13.43 8.26 15.74 15.2 2.08 5.63.36 11.7-3.32 16.23-4.64 5.76-11.76 9.79-18.6 12.35-12.35 4.41-26.02 5.91-38.99 3.9-9.29-1.29-18.98-4.28-26.67-9.78-5.23-3.9-9.63-9.03-9.91-15.85-.19-6.85 4.17-12.52 9.29-16.58 7.13-5.51 16.25-8.79 25.03-10.52Z" fill="#fff"/><path d="M592.23 469.2c2.12-1.59 4.65-.32 5.95 1.66 2.39 4.01 2.38 9.6 2.79 14.14.21 4.39.49 9.64-1.22 13.75-1.98 4.07-7.41 1.34-8.7-1.8-2.28-6.35-2.19-13.75-1.68-20.4.42-2.36.64-5.94 2.86-7.35ZM416.851 476.07c2.18.53 2.81 3.77 3.31 5.63.82 5.03 1.38 10.25 1.06 15.34-.33 2.54-.69 5.69-3.12 7.13-2.07 1.29-4.27-.03-5.35-1.93-2.07-3.84-2.16-8.98-2.29-13.24.06-4.36-.11-8.73 2.24-12.56 1.44-.39 2.65-.96 4.15-.37Z" fill="#000"/></g><g transform="translate(-279 -322)"><g fill="#000"><path d="M545.03 378.5c24.7.94 48.04 8.97 69.45 20.99 2.26 1.32 4.92 2.49 6.79 4.33 1.3 2.39-.38 5.13-3.2 4.19-16.58-6.21-33.56-9.63-51.06-12.02-20.3-2.34-39.92-3.68-60.2-.13-3.2.44-6.87 2.16-10.08 1.59-2.66-1.69-1.2-6.1 1.29-7.37 13.63-9.33 30.79-12.1 47.01-11.58ZM407.99 396.92c3.51.64 6.6 1.92 9.63 3.78-.15 1.2-.26 2.4-.33 3.62-2.7.74-5.46 1.12-8.24 1.34-13.17 1.15-26.45 5.11-38.81 9.69-12.23 4.75-24.78 10.64-35.19 18.71-2.82 2.03-4.13 4.82-8.08 4.47-.5-.9-1-1.79-1.51-2.67.89-1.59 1.68-3.17 2.76-4.64 9.55-13.17 23.64-23.22 38.61-29.35 12.7-5.15 27.61-8.05 41.16-4.95Z"/></g></g><g transform="translate(-279 -322)"><path d="M524.2 555.79c.49 6.88-.8 13.42-1.56 20.21-2.78 24.19-9.38 47.95-19.15 70.23-3.65 7.33-6.74 15.74-13.95 20.34-4.25 3.06-10.3 1.77-14.6-.52-9.49-4.69-15.15-12.28-21.77-20.2-10.97-13.41-18.85-28.33-26.05-44.01-3.56-8.19-6.83-16.5-10.25-24.74-.87-2.13-1.17-4.09 1.07-5.43 4.72-.01 9.31 1.49 14.06 1.51 9.29.24 18.74.4 27.98-.82 19.98-2.29 39.64-8.3 57.99-16.44 2.13-1.07 4.03-.6 6.23-.13Z" fill="#000"/><path d="M518.93 561.99c-1.93 15.16-3.98 30.2-8.01 44.98-4.69 16.77-10.27 33.84-19.05 48.96-2.17 3.44-5.29 7.34-9.89 6.51-7.43-1.55-14.07-7.99-18.68-13.73-9.88-10.97-17.89-23.26-24.61-36.38-6.13-11.12-10.64-22.89-15.69-34.52 4.8.44 9.6.8 14.4 1.2 1.13 5.94 4.59 11.51 8.45 16.07 5.08 5.94 12.23 9.8 20.13 9.97 6.25-.49 12.08-3.13 16.68-7.36 8.02-7.35 11.96-17.07 14.48-27.43 7.39-2.48 14.64-5.15 21.79-8.27Z" fill="#8F2E45"/><path d="M490.65 572.12c-2.55 8.79-7.09 18.39-14.95 23.59-5.47 3.77-12.71 4.39-18.57 1.12-7.01-3.86-10.91-10.5-13.93-17.65 16.1-.33 32.01-2.32 47.45-7.06Z" fill="#fff"/></g><g transform="translate(-279 -322)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

1
assets/img/avatar/4.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" fill="none"><metadata><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>Adventurer Neutral</dc:title><dc:creator><cc:Agent><dc:title>Lisa Wischofsky</dc:title></cc:Agent></dc:creator><dc:source>https://www.instagram.com/lischi_art/</dc:source><cc:license rdf:resource="https://creativecommons.org/licenses/by/4.0/"/></cc:Work><cc:License rdf:about="https://creativecommons.org/licenses/by/4.0/"><cc:permits rdf:resource="https://creativecommons.org/ns#Reproduction"/><cc:permits rdf:resource="https://creativecommons.org/ns#Distribution"/><cc:permits rdf:resource="https://creativecommons.org/ns#DerivativeWorks"/><cc:requires rdf:resource="https://creativecommons.org/ns#Notice"/><cc:requires rdf:resource="https://creativecommons.org/ns#Attribution"/></cc:License></rdf:RDF></metadata><mask id="avatarsRadiusMask"><rect width="400" height="400" rx="0" ry="0" x="0" y="0" fill="#fff"/></mask><g mask="url(#avatarsRadiusMask)"><rect fill="rgba(236, 173, 128, 1)" width="400" height="400" x="0" y="0"/><g transform="translate(-279 -322)"><g fill="#000"><path d="M619.37 468.64c3.87 2.59 8.11 5.27 11.16 8.82.29 1.66.07 3.43.04 5.12-1.93.6-3.41 1.04-5.39.26-29.11-10.45-62.11-9.7-91.33-.07-12.55 4.26-25.01 10.37-35.3 18.8-1.32 1.05-3.89 2.91-5.29 1.13-1.71-1.56-.64-3.53.19-5.23 7.13-12.76 17.99-23.13 30.66-30.34 29.02-16.58 66.88-16.42 95.26 1.51ZM405.49 480.39c11.64 2.56 23.55 8.63 31.21 17.94 2.79 3.9 6.46 8.06 5.87 13.18-1.58-.03-3.27.69-4.53-.5-4.31-3.48-8.75-6.1-13.92-8.1-15.2-5.93-32.51-7.16-48.6-5.04-12.43 1.78-26.06 5.7-35.99 13.69-1.91 1.29-3.18 2.8-5.68 1.81-1.2-1.87-1.02-4.43.33-6.17 16.01-21.77 45.05-31.82 71.31-26.81Z"/></g></g><g transform="translate(-279 -322)"><g fill="#000"><path d="M456.01 380.37c2.26-.07 2.85 1.7 2.65 3.64-.5 4.68-.77 9.78-2.32 14.23-1.33.39-2.66.58-4.03.82-1.12-4.8-.01-10.22.71-15.09.36-1.71.77-3.82 2.99-3.6ZM476.79 386.65c.03 3.21-.36 6.36-.67 9.55 41.69.52 83.32.38 125.01.75 2.13.04 3.96.86 5.89 1.64-.59 1.48-.22 3.83-2.24 4.09-8.05 1.31-16.58.52-24.74.88-17.35.62-34.71.99-52.06 1.53-17.03.2-34.01 1.25-51.05 1.42-3.29.39-6.05-2.72-7.2-5.47.02-4.49 1.02-9.15 2.06-13.5 1.28-2.48 2.83-1.67 5-.89ZM439.84 392.18c-.11 4.87-.05 10.81-2.28 15.24-3.22 1.5-7.12 1.79-10.59 2.47-25.65 4.7-51.32 9.35-76.96 14.09-4.24.69-8.7 2.15-12.99 2.04-2.63-.48-3.33-3.75-.87-5.01 3.2-1.41 6.95-1.74 10.33-2.58 29.1-6.7 58.15-13.59 87.24-20.32.41-1.91.8-3.81 1.27-5.7 1.61-.21 3.23-.24 4.85-.23Z"/></g></g><g transform="translate(-279 -322)"><path d="M505.05 609.63c4.67 1.28 8.87 4.71 12.16 8.17 1.37 1.66 3.56 3.88 3.05 6.2-.46 2.46-3.75 3.02-5.05.83-2.59-3.53-5.52-6.54-9.45-8.58-2.27-1.46-6.72-.53-6.61-4.14.48-3.02 3.52-2.9 5.9-2.48ZM498 622.96c4.97 1.51 8.45 5.86 8.78 11.06 1.3 13.18-9.39 25.8-21.75 29.19-12.46 3.66-28.87-1.1-35.07-13.17-2.47-4.21-3.73-9.26-1.94-13.96 1.78-4.05 5.82-5.66 9.97-6.1 5.09-.59 9.55 1.11 13.9 3.54 3.82-4.04 7.74-7.59 12.91-9.81 4.07-1.76 8.96-1.98 13.2-.75Z" fill="#000"/><path d="M498.91 630.09c3.25 3.38 2.26 9.5.47 13.39-4.28 10.05-15.11 16.13-25.9 14.99-7.3-1.19-14.31-4.11-18.2-10.76-1.43-2.56-3.43-6.58-2.01-9.44 2.28-3.76 8.77-3.17 12.28-1.81 2.75.77 5.53 4.09 8.39 3.59 3.67-3.56 6.48-7.47 11.18-9.88 4.32-2.4 9.63-3.3 13.79-.08Z" fill="#8F2E45"/></g><g transform="translate(-279 -322)"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/img/avatar/user.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
assets/img/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"/><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"/><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"/><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"/></svg>

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 262 B

View File

@ -0,0 +1 @@
<svg id="SvgjsSvg1001" width="288" height="288" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs"><defs id="SvgjsDefs1002"></defs><g id="SvgjsG1008"><svg xmlns="http://www.w3.org/2000/svg" width="288" height="288" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M8.72 18.78a.75.75 0 001.06 0l6.25-6.25a.75.75 0 000-1.06L9.78 5.22a.75.75 0 00-1.06 1.06L14.44 12l-5.72 5.72a.75.75 0 000 1.06z" fill="#d1d5db" class="color000 svgShape"></path></svg></g></svg>

After

Width:  |  Height:  |  Size: 536 B

View File

@ -0,0 +1 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.72 18.78a.75.75 0 001.06 0l6.25-6.25a.75.75 0 000-1.06L9.78 5.22a.75.75 0 00-1.06 1.06L14.44 12l-5.72 5.72a.75.75 0 000 1.06z"/></svg>

After

Width:  |  Height:  |  Size: 253 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
assets/img/loader.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -0,0 +1,20 @@
<svg width="25" height="29" viewBox="0 0 25 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3689 2.39562C16.0712 1.56892 17.439 2.13387 17.3279 3.20488L16.5434 10.7281H22.8893C23.1004 10.7282 23.3071 10.7873 23.4853 10.8985C23.6634 11.0098 23.8056 11.1685 23.8951 11.3562C23.9847 11.5439 24.0179 11.7527 23.9909 11.9582C23.9639 12.1638 23.8778 12.3575 23.7426 12.5167L12.6311 25.6044C11.9288 26.4311 10.561 25.8661 10.6721 24.7951L11.4566 17.2719H5.11074C4.89961 17.2718 4.69286 17.2127 4.51472 17.1015C4.33658 16.9902 4.19442 16.8315 4.10487 16.6438C4.01533 16.4561 3.98212 16.2473 4.00913 16.0418C4.03614 15.8362 4.12225 15.6425 4.25737 15.4833L15.3689 2.39562ZM7.48306 15.0906H13.4255C13.4876 15.0906 13.5491 15.1034 13.6059 15.1282C13.6626 15.1529 13.7135 15.189 13.7552 15.2342C13.7968 15.2795 13.8284 15.3327 13.8478 15.3907C13.8672 15.4486 13.874 15.5099 13.8678 15.5705L13.2533 21.4654L20.5169 12.9094H14.5745C14.5124 12.9094 14.4509 12.8966 14.3942 12.8719C14.3374 12.8471 14.2865 12.811 14.2448 12.7658C14.2032 12.7205 14.1716 12.6673 14.1522 12.6093C14.1328 12.5514 14.126 12.4901 14.1322 12.4295L14.7467 6.53568L7.48306 15.0906Z" fill="#323232"/>
<g filter="url(#filter0_d_1_181)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9374 1.41211C14.6748 0.550959 16.111 1.13945 15.9943 2.25508L15.1706 10.0918H21.8337C22.0554 10.0918 22.2725 10.1534 22.4595 10.2693C22.6466 10.3852 22.7959 10.5505 22.8899 10.746C22.9839 10.9415 23.0188 11.1591 22.9904 11.3732C22.9621 11.5873 22.8716 11.7891 22.7298 11.9549L11.0626 25.5879C10.3252 26.449 8.88902 25.8606 9.00569 24.7449L9.82939 16.9082H3.16628C2.94459 16.9082 2.72751 16.8466 2.54046 16.7307C2.35341 16.6148 2.20414 16.4495 2.11012 16.254C2.0161 16.0585 1.98123 15.8409 2.00959 15.6268C2.03794 15.4127 2.12836 15.2109 2.27024 15.0451L13.9374 1.41211ZM5.65722 14.6361H11.8968C11.962 14.6361 12.0265 14.6494 12.0861 14.6752C12.1458 14.7009 12.1992 14.7386 12.2429 14.7857C12.2867 14.8328 12.3198 14.8883 12.3402 14.9486C12.3605 15.0089 12.3677 15.0728 12.3612 15.136L11.716 21.2765L19.3428 12.3639H13.1032C13.038 12.3639 12.9735 12.3506 12.9139 12.3248C12.8542 12.2991 12.8008 12.2614 12.7571 12.2143C12.7133 12.1672 12.6802 12.1117 12.6598 12.0514C12.6395 11.9911 12.6323 11.9272 12.6388 11.864L13.284 5.72467L5.65722 14.6361Z" fill="#D9D9D9"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5675 14.693L2.26832 15.0451C2.12745 15.211 2.03767 15.4128 2.00952 15.6269C1.98136 15.841 2.01598 16.0585 2.10933 16.254C2.20268 16.4495 2.3509 16.6149 2.53661 16.7307C2.72233 16.8466 2.93787 16.9082 3.15798 16.9083H9.77369L8.95585 24.7449C8.84001 25.8606 10.266 26.4491 10.9981 25.5879L20.2557 14.693H17.2405L11.6468 21.2765L12.2875 15.136C12.2939 15.0728 12.2868 15.009 12.2666 14.9486C12.2464 14.8883 12.2135 14.8328 12.17 14.7857C12.1357 14.7485 12.0955 14.7172 12.0509 14.693H2.5675Z" fill="#6AA39C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9497 2.25508C16.0655 1.13945 14.6395 0.550959 13.9074 1.41211L2.62244 14.693H12.1058C12.0939 14.6865 12.0818 14.6806 12.0693 14.6751C12.0101 14.6494 11.9461 14.6361 11.8813 14.6361H5.68612L13.2587 5.72467L12.6181 11.864C12.6116 11.9272 12.6187 11.9911 12.6389 12.0514C12.6592 12.1117 12.692 12.1672 12.7355 12.2143C12.7789 12.2614 12.832 12.2991 12.8911 12.3248C12.9503 12.3506 13.0144 12.3639 13.0791 12.3639H19.2743L17.2955 14.693H20.3107L22.6372 11.9549C22.7781 11.7891 22.8679 11.5873 22.896 11.3732C22.9242 11.1591 22.8896 10.9415 22.7962 10.746C22.7029 10.5505 22.5546 10.3852 22.3689 10.2693C22.1832 10.1534 21.9677 10.0918 21.7476 10.0918H15.1318L15.9497 2.25508Z" fill="#FFC151"/>
<defs>
<filter id="filter0_d_1_181" x="0.4" y="0.4" width="24.2" height="28.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.8"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_181"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_181" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

38
assets/img/logo/logo.svg Normal file
View File

@ -0,0 +1,38 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="39" height="39" rx="4.5" fill="black" stroke="#323232"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3689 9.39562C23.0712 8.56892 24.439 9.13387 24.3279 10.2049L23.5434 17.7281H29.8893C30.1004 17.7282 30.3071 17.7873 30.4853 17.8985C30.6634 18.0098 30.8056 18.1685 30.8951 18.3562C30.9847 18.5439 31.0179 18.7527 30.9909 18.9582C30.9639 19.1638 30.8778 19.3575 30.7426 19.5167L19.6311 32.6044C18.9288 33.4311 17.561 32.8661 17.6721 31.7951L18.4566 24.2719H12.1107C11.8996 24.2718 11.6929 24.2127 11.5147 24.1015C11.3366 23.9902 11.1944 23.8315 11.1049 23.6438C11.0153 23.4561 10.9821 23.2473 11.0091 23.0418C11.0361 22.8362 11.1222 22.6425 11.2574 22.4833L22.3689 9.39562ZM14.4831 22.0906H20.4255C20.4876 22.0906 20.5491 22.1034 20.6059 22.1282C20.6626 22.1529 20.7135 22.189 20.7552 22.2342C20.7968 22.2795 20.8284 22.3327 20.8478 22.3907C20.8672 22.4486 20.874 22.5099 20.8678 22.5705L20.2533 28.4654L27.5169 19.9094H21.5745C21.5124 19.9094 21.4509 19.8966 21.3942 19.8719C21.3374 19.8471 21.2865 19.811 21.2448 19.7658C21.2032 19.7205 21.1716 19.6673 21.1522 19.6093C21.1328 19.5514 21.126 19.4901 21.1322 19.4295L21.7467 13.5357L14.4831 22.0906Z" fill="#323232"/>
<g filter="url(#filter0_d_1_180)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.9374 8.41211C21.6748 7.55096 23.111 8.13945 22.9943 9.25508L22.1706 17.0918H28.8337C29.0554 17.0918 29.2725 17.1534 29.4595 17.2693C29.6466 17.3852 29.7959 17.5505 29.8899 17.746C29.9839 17.9415 30.0188 18.1591 29.9904 18.3732C29.9621 18.5873 29.8716 18.7891 29.7298 18.9549L18.0626 32.5879C17.3252 33.449 15.889 32.8606 16.0057 31.7449L16.8294 23.9082H10.1663C9.94459 23.9082 9.72751 23.8466 9.54046 23.7307C9.35341 23.6148 9.20414 23.4495 9.11012 23.254C9.0161 23.0585 8.98123 22.8409 9.00959 22.6268C9.03794 22.4127 9.12836 22.2109 9.27024 22.0451L20.9374 8.41211ZM12.6572 21.6361H18.8968C18.962 21.6361 19.0265 21.6494 19.0861 21.6752C19.1458 21.7009 19.1992 21.7386 19.2429 21.7857C19.2867 21.8328 19.3198 21.8883 19.3402 21.9486C19.3605 22.0089 19.3677 22.0728 19.3612 22.136L18.716 28.2765L26.3428 19.3639H20.1032C20.038 19.3639 19.9735 19.3506 19.9139 19.3248C19.8542 19.2991 19.8008 19.2614 19.7571 19.2143C19.7133 19.1672 19.6802 19.1117 19.6598 19.0514C19.6395 18.9911 19.6323 18.9272 19.6388 18.864L20.284 12.7247L12.6572 21.6361Z" fill="#D9D9D9"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5675 21.693L9.26832 22.0451C9.12745 22.211 9.03767 22.4128 9.00952 22.6269C8.98136 22.841 9.01598 23.0585 9.10933 23.254C9.20268 23.4495 9.3509 23.6149 9.53661 23.7307C9.72233 23.8466 9.93787 23.9082 10.158 23.9083H16.7737L15.9559 31.7449C15.84 32.8606 17.266 33.4491 17.9981 32.5879L27.2557 21.693H24.2405L18.6468 28.2765L19.2875 22.136C19.2939 22.0728 19.2868 22.009 19.2666 21.9486C19.2464 21.8883 19.2135 21.8328 19.17 21.7857C19.1357 21.7485 19.0955 21.7172 19.0509 21.693H9.5675Z" fill="#6AA39C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.9497 9.25508C23.0655 8.13945 21.6395 7.55096 20.9074 8.41211L9.62244 21.693H19.1058C19.0939 21.6865 19.0818 21.6806 19.0693 21.6751C19.0101 21.6494 18.9461 21.6361 18.8813 21.6361H12.6861L20.2587 12.7247L19.6181 18.864C19.6116 18.9272 19.6187 18.9911 19.6389 19.0514C19.6592 19.1117 19.692 19.1672 19.7355 19.2143C19.7789 19.2614 19.832 19.2991 19.8911 19.3248C19.9503 19.3506 20.0144 19.3639 20.0791 19.3639H26.2743L24.2955 21.693H27.3107L29.6372 18.9549C29.7781 18.7891 29.8679 18.5873 29.896 18.3732C29.9242 18.1591 29.8896 17.9415 29.7962 17.746C29.7029 17.5505 29.5546 17.3852 29.3689 17.2693C29.1832 17.1534 28.9677 17.0918 28.7476 17.0918H22.1318L22.9497 9.25508Z" fill="#FFC151"/>
<rect x="0.5" y="0.5" width="39" height="39" rx="4.5" fill="black" stroke="#323232"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3689 9.39562C23.0712 8.56892 24.439 9.13387 24.3279 10.2049L23.5434 17.7281H29.8893C30.1004 17.7282 30.3071 17.7873 30.4853 17.8985C30.6634 18.0098 30.8056 18.1685 30.8951 18.3562C30.9847 18.5439 31.0179 18.7527 30.9909 18.9582C30.9639 19.1638 30.8778 19.3575 30.7426 19.5167L19.6311 32.6044C18.9288 33.4311 17.561 32.8661 17.6721 31.7951L18.4566 24.2719H12.1107C11.8996 24.2718 11.6929 24.2127 11.5147 24.1015C11.3366 23.9902 11.1944 23.8315 11.1049 23.6438C11.0153 23.4561 10.9821 23.2473 11.0091 23.0418C11.0361 22.8362 11.1222 22.6425 11.2574 22.4833L22.3689 9.39562ZM14.4831 22.0906H20.4255C20.4876 22.0906 20.5491 22.1034 20.6059 22.1282C20.6626 22.1529 20.7135 22.189 20.7552 22.2342C20.7968 22.2795 20.8284 22.3327 20.8478 22.3907C20.8672 22.4486 20.874 22.5099 20.8678 22.5705L20.2533 28.4654L27.5169 19.9094H21.5745C21.5124 19.9094 21.4509 19.8966 21.3942 19.8719C21.3374 19.8471 21.2865 19.811 21.2448 19.7658C21.2032 19.7205 21.1716 19.6673 21.1522 19.6093C21.1328 19.5514 21.126 19.4901 21.1322 19.4295L21.7467 13.5357L14.4831 22.0906Z" fill="#323232"/>
<g filter="url(#filter1_d_1_180)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.9374 8.41211C21.6748 7.55096 23.111 8.13945 22.9943 9.25508L22.1706 17.0918H28.8337C29.0554 17.0918 29.2725 17.1534 29.4595 17.2693C29.6466 17.3852 29.7959 17.5505 29.8899 17.746C29.9839 17.9415 30.0188 18.1591 29.9904 18.3732C29.9621 18.5873 29.8716 18.7891 29.7298 18.9549L18.0626 32.5879C17.3252 33.449 15.889 32.8606 16.0057 31.7449L16.8294 23.9082H10.1663C9.94459 23.9082 9.72751 23.8466 9.54046 23.7307C9.35341 23.6148 9.20414 23.4495 9.11012 23.254C9.0161 23.0585 8.98123 22.8409 9.00959 22.6268C9.03794 22.4127 9.12836 22.2109 9.27024 22.0451L20.9374 8.41211ZM12.6572 21.6361H18.8968C18.962 21.6361 19.0265 21.6494 19.0861 21.6752C19.1458 21.7009 19.1992 21.7386 19.2429 21.7857C19.2867 21.8328 19.3198 21.8883 19.3402 21.9486C19.3605 22.0089 19.3677 22.0728 19.3612 22.136L18.716 28.2765L26.3428 19.3639H20.1032C20.038 19.3639 19.9735 19.3506 19.9139 19.3248C19.8542 19.2991 19.8008 19.2614 19.7571 19.2143C19.7133 19.1672 19.6802 19.1117 19.6598 19.0514C19.6395 18.9911 19.6323 18.9272 19.6388 18.864L20.284 12.7247L12.6572 21.6361Z" fill="#D9D9D9"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5675 21.693L9.26832 22.0451C9.12745 22.211 9.03767 22.4128 9.00952 22.6269C8.98136 22.841 9.01598 23.0585 9.10933 23.254C9.20268 23.4495 9.3509 23.6149 9.53661 23.7307C9.72233 23.8466 9.93787 23.9082 10.158 23.9083H16.7737L15.9559 31.7449C15.84 32.8606 17.266 33.4491 17.9981 32.5879L27.2557 21.693H24.2405L18.6468 28.2765L19.2875 22.136C19.2939 22.0728 19.2868 22.009 19.2666 21.9486C19.2464 21.8883 19.2135 21.8328 19.17 21.7857C19.1357 21.7485 19.0955 21.7172 19.0509 21.693H9.5675Z" fill="#6AA39C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.9497 9.25508C23.0655 8.13945 21.6395 7.55096 20.9074 8.41211L9.62244 21.693H19.1058C19.0939 21.6865 19.0818 21.6806 19.0693 21.6751C19.0101 21.6494 18.9461 21.6361 18.8813 21.6361H12.6861L20.2587 12.7247L19.6181 18.864C19.6116 18.9272 19.6187 18.9911 19.6389 19.0514C19.6592 19.1117 19.692 19.1672 19.7355 19.2143C19.7789 19.2614 19.832 19.2991 19.8911 19.3248C19.9503 19.3506 20.0144 19.3639 20.0791 19.3639H26.2743L24.2955 21.693H27.3107L29.6372 18.9549C29.7781 18.7891 29.8679 18.5873 29.896 18.3732C29.9242 18.1591 29.8896 17.9415 29.7962 17.746C29.7029 17.5505 29.5546 17.3852 29.3689 17.2693C29.1832 17.1534 28.9677 17.0918 28.7476 17.0918H22.1318L22.9497 9.25508Z" fill="#FFC151"/>
<defs>
<filter id="filter0_d_1_180" x="7.4" y="7.4" width="24.2" height="28.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.8"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_180"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_180" result="shape"/>
</filter>
<filter id="filter1_d_1_180" x="7.4" y="7.4" width="24.2" height="28.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.8"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_180"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_180" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" viewBox="0 0 64 64" version="1.1"><rect fill="#ddd" cx="32" width="64" height="64" cy="32" r="32"/><text x="50%" y="50%" style="color: #222; line-height: 1;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;" alignment-baseline="middle" text-anchor="middle" font-size="28" font-weight="400" dy=".1em" dominant-baseline="middle" fill="#222">JD</text></svg>

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -0,0 +1,16 @@
import { createInput } from "@formkit/vue";
import OneTimePassword from "~/components/formkit/OneTimePassword.vue";
import MaskText from "~/components/formkit/TextMask.vue";
import FileDropzone from "~/components/formkit/FileDropzone.vue";
export default {
otp: createInput(OneTimePassword, {
props: ["digits"],
}),
mask: createInput(MaskText, {
props: ["mask"],
}),
dropzone: createInput(FileDropzone, {
props: ["accept", "multiple", "maxSize", "minSize", "maxFiles", "disabled"],
}),
};

View File

@ -0,0 +1,94 @@
// Create some re-useable definitions because
// many input types are identical in how
// we want to style them.
const textClassification = {
label: "formkit-outer-text",
inner: "formkit-inner-text",
input: "formkit-input-text",
prefix: "formkit-prefix-text",
message: "formkit-message-text",
};
const boxClassification = {
inner: "formkit-inner-box",
fieldset: "formkit-fieldset-box",
legend: "formkit-legend-box",
wrapper: "formkit-wrapper-box",
help: "formkit-help-box",
input: "formkit-input-box",
label: "formkit-label-box",
message: "formkit-message-box",
};
const buttonClassification = {
wrapper: "formkit-wrapper-button",
input: "formkit-input-button",
};
const OtpClassification = {
label: "formkit-label-otp",
inner: "formkit-inner-otp",
digit: "formkit-digit-otp",
message: "formkit-message-otp",
};
const colorClassification = {
label: "formkit-label-color",
input: "formkit-input-color",
};
const fileClassification = {
label: "formkit-label-file",
inner: "formkit-inner-file",
input: "formkit-input-file",
};
const rangeClassification = {
input: "formkit-input-range",
};
// export our definitions using our above
// templates and declare one-offs and
// overrides as needed.
export default {
// the global key will apply to all inputs
global: {
label: "formkit-label-global",
outer: "formkit-outer-global",
help: "formkit-help-global",
messages: "formkit-messages-global",
message: "formkit-message-global",
wrapper: "formkit-wrapper-global",
},
button: buttonClassification,
color: colorClassification,
date: textClassification,
"datetime-local": textClassification,
checkbox: boxClassification,
email: textClassification,
file: fileClassification,
month: textClassification,
number: textClassification,
password: textClassification,
radio: {
...boxClassification,
input: "formkit-input-radio",
},
range: rangeClassification,
search: textClassification,
select: { ...textClassification, option: "p-2" },
submit: buttonClassification,
tel: textClassification,
text: textClassification,
textarea: {
...textClassification,
input: "formkit-input-textarea",
},
time: textClassification,
url: textClassification,
week: textClassification,
otp: OtpClassification,
mask: textClassification,
dropzone: {
...textClassification,
inner: "formkit-inner-dropzone",
dropzone: "formkit-dropzone",
},
};

View File

@ -0,0 +1,126 @@
html[data-theme="default"] {
--color-primary: 13, 27, 42;
--color-secondary: 97, 176, 183;
--color-accent: 243, 244, 246;
--color-success: 79, 192, 103;
--color-info: 65, 133, 242;
--color-warning: 246, 141, 32;
--color-danger: 229, 83, 69;
--text-color: 9, 9, 11;
--border-color: 228, 228, 231;
--bg-1: 243, 244, 246;
--bg-2: 255, 255, 255;
--scroll-color: 170, 170, 170;
--scroll-hover-color: 155, 155, 155;
--fk-border-color: 228, 228, 231;
--fk-placeholder-color: 146, 146, 153;
--box-shadow: rgba(36, 35, 71, 0.05) 0px 1px 1px,
rgba(36, 35, 71, 0.05) 0px 0px 1px 1px;
--cp-bg: 255, 255, 255;
--rounded-box: 0.2rem;
--rounded-btn: 0.2rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="dark"] {
--color-primary: 97, 176, 183;
--color-secondary: 13, 27, 42;
--color-accent: 15, 23, 42;
--color-success: 79, 192, 103;
--color-info: 65, 133, 242;
--color-warning: 246, 141, 32;
--color-danger: 229, 83, 69;
--text-color: 209, 213, 219;
--border-color: 30, 41, 59;
--bg-1: 15, 23, 42;
--bg-2: 30, 41, 59;
--scroll-color: 170, 170, 170;
--scroll-hover-color: 155, 155, 155;
--fk-border-color: 71, 85, 105;
--fk-placeholder-color: 71, 85, 105;
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 1.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="nier"] {
--color-primary: 99, 95, 84;
--color-secondary: 207, 107, 83;
--color-accent: 243, 88, 106;
--color-success: 79, 192, 103;
--color-info: 65, 133, 242;
--color-warning: 246, 141, 32;
--color-danger: 229, 83, 69;
--text-color: 78, 75, 66;
--border-color: 153, 152, 131;
--bg-1: 205, 200, 176;
--bg-2: 218, 212, 187;
--scroll-color: 99, 95, 84;
--scroll-hover-color: 99, 95, 84;
--fk-border-color: 99, 95, 84;
--fk-placeholder-color: 188, 175, 145;
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 0.875rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="custom1"] {
--color-primary: 9, 76, 90;
--color-secondary: 62, 72, 83;
--color-accent: 0, 103, 236;
--color-success: 11, 152, 76;
--color-info: 208, 89, 41;
--color-warning: 229, 197, 13;
--color-danger: 238, 5, 34;
--text-color: 15, 15, 14;
--border-color: 153, 152, 131;
--bg-1: 214, 215, 206;
--bg-2: 235, 232, 217;
--scroll-color: 9, 76, 90;
--scroll-hover-color: 9, 76, 90;
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 1.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}

View File

@ -0,0 +1,28 @@
/* RS Alert Component */
.alert {
@apply visible flex items-center justify-between py-3 px-3 rounded-lg;
}
.alert.alert-primary {
@apply bg-primary/20 text-primary;
}
.alert.alert-secondary {
@apply bg-secondary/20 text-secondary;
}
.alert.alert-success {
@apply bg-success/20 text-success;
}
.alert.alert-info {
@apply bg-info/20 text-info;
}
.alert.alert-warning {
@apply bg-warning/20 text-warning;
}
.alert.alert-danger {
@apply bg-danger/20 text-danger;
}

View File

@ -0,0 +1,28 @@
/* RS Badge Component */
.badge {
@apply inline-flex items-center justify-center px-2 py-1 rounded-full text-xs font-semibold leading-none;
}
.badge.badge-primary {
@apply bg-primary text-white;
}
.badge.badge-secondary {
@apply bg-secondary text-white;
}
.badge.badge-success {
@apply bg-success text-white;
}
.badge.badge-info {
@apply bg-info text-white;
}
.badge.badge-warning {
@apply bg-warning text-white;
}
.badge.badge-danger {
@apply bg-danger text-white;
}

View File

@ -0,0 +1,158 @@
/* RS Button */
.button {
@apply w-fit rounded-lg flex justify-center items-center h-fit;
}
.button[class*="button-"]:hover {
background-image: linear-gradient(rgb(0 0 0/10%) 0 0);
}
.button[class*="button-"]:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.button.button-primary {
@apply bg-primary text-white;
}
.button.button-secondary {
@apply bg-secondary text-white;
}
.button.button-success {
@apply bg-success text-white;
}
.button.button-info {
@apply bg-info text-white;
}
.button.button-warning {
@apply bg-warning text-white;
}
.button.button-danger {
@apply bg-danger text-white;
}
.button[class*="outline-"]:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.button.outline-primary {
@apply border border-primary text-primary;
}
.button.outline-primary:hover {
@apply bg-primary/5;
}
.button.outline-secondary {
@apply border border-secondary text-secondary;
}
.button.outline-secondary:hover {
@apply bg-secondary/5;
}
.button.outline-success {
@apply border border-success text-success;
}
.button.outline-success:hover {
@apply bg-success/5;
}
.button.outline-info {
@apply border border-info text-info;
}
.button.outline-info:hover {
@apply bg-info/5;
}
.button.outline-warning {
@apply border border-warning text-warning;
}
.button.outline-warning:hover {
@apply bg-warning/5;
}
.button.outline-danger {
@apply border border-danger text-danger;
}
.button.outline-danger:hover {
@apply bg-danger/5;
}
.button[class*="text-"]:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.button.texts-primary {
@apply text-primary;
}
.button.texts-primary:hover {
@apply bg-primary/10;
}
.button.texts-secondary {
@apply text-secondary;
}
.button.texts-secondary:hover {
@apply bg-secondary/10;
}
.button.texts-success {
@apply text-success;
}
.button.texts-success:hover {
@apply bg-success/10;
}
.button.texts-info {
@apply text-info;
}
.button.texts-info:hover {
@apply bg-info/10;
}
.button.texts-warning {
@apply text-warning;
}
.button.texts-warning:hover {
@apply bg-warning/10;
}
.button.texts-danger {
@apply text-danger;
}
.button.texts-danger:hover {
@apply bg-danger/10;
}
.button-sm {
@apply text-xs;
padding: var(--padding-btn);
}
.button-md {
@apply text-sm;
padding: var(--padding-btn);
}
.button-lg {
@apply text-base;
padding: var(--padding-btn);
}

View File

@ -0,0 +1,19 @@
/* RS Card Component */
.card {
@apply mb-6;
background-color: rgb(var(--bg-2));
border-radius: var(--rounded-box);
box-shadow: var(--box-shadow);
}
.card .card-header {
@apply text-xl p-5 font-medium;
}
.card .card-body {
@apply px-5 pb-5;
}
.card .card-footer {
@apply px-5 pb-5;
}

View File

@ -0,0 +1,61 @@
/* RS Collapse Component */
.accordion {
@apply w-full mb-4 visible;
background-color: rgb(var(--bg-2));
}
.accordion.accordion-border {
@apply border-t border-x rounded-lg;
border-color: rgb(var(--border));
}
.accordion .accordion-group {
@apply overflow-hidden duration-300 dark:border-slate-700;
background-color: rgb(var(--bg-2));
}
.accordion .accordion-group.accordion-default {
@apply border-b;
border-color: rgb(var(--border));
}
.accordion .accordion-group.accordion-border {
@apply border-b last:rounded-b-lg;
border-color: rgb(var(--border));
}
.accordion .accordion-group.accordion-card {
@apply my-4;
box-shadow: var(--box-shadow);
}
.accordion .accordion-group .accordion-header {
@apply text-lg font-medium px-5 pr-10 py-4 cursor-pointer;
}
.accordion .accordion-group .accordion-body {
@apply px-5 pb-4 pt-2;
}
.accordion-group .accordion-header {
position: relative;
}
.accordion-group .accordion-header::after {
content: "";
position: absolute;
width: 19px;
height: 19px;
top: 50%;
right: 0%;
transform: translate(-100%, -50%) rotate(0deg);
background: url("/assets/img/icon/chevron-right.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
transition: 0.25s ease-in-out;
}
.accordion-group--open .accordion-header::after {
transform: translate(-100%, -50%) rotate(90deg);
}

View File

@ -0,0 +1,42 @@
body {
color: rgb(var(--text-color));
background-color: rgb(var(--bg-1));
}
.w-header {
@apply z-20 fixed top-0 right-0 px-5 py-3 duration-300 shadow-md;
background-color: rgb(var(--bg-2));
box-shadow: var(--box-shadow);
}
.w-header-search {
@apply px-4 z-40 duration-300 shadow-md -top-20 focus-within:top-0 right-0;
background-color: rgb(var(--bg-2));
}
.vertical-menu {
@apply text-base h-screen fixed w-64 top-0 z-50 duration-300 border-l-0 shadow-md;
background-color: rgb(var(--bg-2));
box-shadow: var(--box-shadow);
}
.icon-btn {
@apply flex
items-center
justify-center
transition-colors
duration-300;
color: rgb(var(--text-color));
}
.icon-btn.profile {
@apply border;
border-radius: var(--rounded-box);
border: 1px solid rgb(var(--border-color));
color: rgb(var(--text-color));
}
.icon-btn:hover {
color: rgb(var(--text-color));
background-color: rgb(var(--bg-1));
}

View File

@ -0,0 +1,53 @@
/* RS Dropdown Component */
.dropdown {
@apply relative inline-flex;
}
.dropdown .dropdown-section {
@apply absolute z-10 shadow-md rounded-lg py-1 whitespace-nowrap;
background-color: rgb(var(--bg-2));
}
.dropdown-section.list-bottom-sm {
@apply top-10;
}
.dropdown-section.list-bottom-md {
@apply top-12;
}
.dropdown-section.list-bottom-lg {
@apply top-16;
}
.dropdown-section.list-top-sm {
@apply bottom-10;
}
.dropdown-section.list-top-md {
@apply bottom-12;
}
.dropdown-section.list-top-lg {
@apply bottom-16;
}
.dropdown-section.list-left {
@apply top-0 -left-[10.5rem];
}
.dropdown-section.list-right {
@apply top-0 -right-[10.5rem];
}
.dropdown-section.list-align-right {
@apply right-0;
}
.dropdown-section .dropdown-item {
@apply flex items-center cursor-pointer px-4 py-2;
}
.dropdown-section .dropdown-item:hover {
background-color: rgb(var(--bg-1));
}

View File

@ -0,0 +1,50 @@
/* RS Modal Component */
.modal {
@apply fixed top-0 left-0 w-full h-full overflow-hidden z-[1000] duration-300;
background: rgba(15, 23, 42, 0.5);
}
.modal.modal-top {
@apply flex items-start;
}
.modal.modal-center {
@apply flex items-center;
}
.modal.modal-end {
@apply flex items-end;
}
.modal.modal-hide-overlay {
@apply !bg-transparent;
}
.modal .modal-dialog {
@apply w-full md:w-auto relative z-[9999];
margin: 1.75rem auto;
}
.modal .modal-dialog .modal-content {
@apply border-none shadow-lg relative flex flex-col w-full pointer-events-auto bg-clip-padding rounded-md outline-none text-current;
background-color: rgb(var(--bg-2));
}
.modal .modal-dialog .modal-content .modal-header {
@apply flex flex-shrink-0 items-center justify-between p-4 border-b rounded-t-md;
border-color: rgb(var(--border-color));
}
.modal .modal-dialog .modal-content .modal-body {
@apply relative p-4;
}
.modal .modal-dialog .modal-content .modal-footer {
@apply flex flex-shrink-0 flex-wrap items-center justify-end px-4 pb-4 rounded-b-md gap-x-3;
}
@media screen and (max-width: 768px) {
.modal-dialog {
margin: 1.75rem 1rem;
}
}

View File

@ -0,0 +1,80 @@
/* RS Progress Component */
.progress-wrapper {
@apply w-full mb-4;
}
.progress-wrapper .progress-label {
@apply text-sm mb-1;
}
.progress-wrapper .progress {
@apply flex rounded-full;
}
.progress-wrapper .progress.progress-sm {
@apply h-3;
}
.progress-wrapper .progress.progress-md {
@apply h-4;
}
.progress-wrapper .progress.progress-lg {
@apply h-5;
}
.progress-wrapper .progress.progress-sm {
@apply h-3;
}
.progress-wrapper .progress.progress-primary {
@apply bg-primary/20;
}
.progress-wrapper .progress.progress-secondary {
@apply bg-secondary/20;
}
.progress-wrapper .progress.progress-success {
@apply bg-success/20;
}
.progress-wrapper .progress.progress-info {
@apply bg-info/20;
}
.progress-wrapper .progress.progress-warning {
@apply bg-warning/20;
}
.progress-wrapper .progress.progress-danger {
@apply bg-danger/20;
}
.progress-wrapper .progress .progress-bar {
@apply flex items-center justify-center rounded-full text-white;
}
.progress-wrapper .progress .progress-bar.primary {
@apply bg-primary;
}
.progress-wrapper .progress .progress-bar.secondary {
@apply bg-secondary;
}
.progress-wrapper .progress .progress-bar.success {
@apply bg-success;
}
.progress-wrapper .progress .progress-bar.info {
@apply bg-info;
}
.progress-wrapper .progress .progress-bar.warning {
@apply bg-warning;
}
.progress-wrapper .progress .progress-bar.danger {
@apply bg-danger;
}

View File

@ -0,0 +1,323 @@
/* RS Tab Component */
.tab {
@apply rounded-md;
}
.tab.vertical {
@apply flex flex-col md:flex-row;
}
.tab.tab-card {
@apply shadow-md pt-4;
}
.tab.card-vertical {
@apply shadow-md;
}
.tab.card-primary {
@apply bg-primary;
}
.tab.card-secondary {
@apply bg-secondary;
}
.tab.card-success {
@apply bg-success;
}
.tab.card-info {
@apply bg-info;
}
.tab.card-warning {
@apply bg-warning;
}
.tab.card-danger {
@apply bg-danger;
}
.tab .tab-nav {
@apply flex flex-wrap list-none pl-0;
}
.tab .tab-nav.tab-nav-card {
@apply mx-4 mb-0;
}
.tab .tab-nav.card-vertical {
@apply py-0 pt-4 md:py-4;
}
.tab .tab-nav.vertical {
@apply flex-row md:flex-col gap-2;
}
.tab .tab-nav.vertical-fill {
@apply flex-1;
}
.tab .tab-nav .tab-item {
@apply cursor-pointer;
}
.tab .tab-nav .tab-item.fill {
@apply flex-1 w-full;
}
.tab .tab-nav .tab-item.border-horizontal {
@apply border-0 hover:border hover:border-b-0 rounded-t-md px-6;
}
.tab .tab-nav .tab-item.border-horizontal-active {
@apply border rounded-t-md border-b-0;
}
.tab .tab-nav .tab-item.border-vertical {
@apply border-0 hover:border hover:border-r-0 rounded-l-md px-6;
}
.tab .tab-nav .tab-item.border-vertical-active {
@apply border rounded-t-md rounded-bl-none md:rounded-r-none md:rounded-l-md border-r border-b-0 md:border-b md:border-r-0;
}
.tab .tab-nav .tab-item.border-hover-primary {
@apply hover:border-primary;
}
.tab .tab-nav .tab-item.border-hover-secondary {
@apply hover:border-secondary;
}
.tab .tab-nav .tab-item.border-hover-success {
@apply hover:border-success;
}
.tab .tab-nav .tab-item.border-hover-info {
@apply hover:border-info;
}
.tab .tab-nav .tab-item.border-hover-warning {
@apply hover:border-warning;
}
.tab .tab-nav .tab-item.border-hover-danger {
@apply hover:border-danger;
}
.tab .tab-nav .tab-item.border-active-primary {
@apply border-primary text-primary;
}
.tab .tab-nav .tab-item.border-active-secondary {
@apply border-secondary text-secondary;
}
.tab .tab-nav .tab-item.border-active-success {
@apply border-success text-success;
}
.tab .tab-nav .tab-item.border-active-info {
@apply border-info text-info;
}
.tab .tab-nav .tab-item.border-active-warning {
@apply border-warning text-warning;
}
.tab .tab-nav .tab-item.border-active-danger {
@apply border-danger text-danger;
}
.tab .tab-nav .tab-item .tab-item-link {
@apply block font-medium text-base leading-tight capitalize border-x-0 border-t-0 py-3;
}
.tab .tab-nav .tab-item .tab-item-link.default {
@apply hover:border-b-2 mx-2 px-4;
}
.tab .tab-nav .tab-item .tab-item-link.default-vertical {
@apply hover:border-l-2 mx-2 px-4;
}
.tab .tab-nav .tab-item .tab-item-link.default-active {
@apply border-b-2;
}
.tab .tab-nav .tab-item .tab-item-link.default-vertical-active {
@apply border-l-2;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-primary {
@apply hover:border-primary hover:text-primary;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-secondary {
@apply hover:border-secondary hover:text-secondary;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-success {
@apply hover:border-success hover:text-success;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-info {
@apply hover:border-info hover:text-info;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-warning {
@apply hover:border-warning hover:text-warning;
}
.tab .tab-nav .tab-item .tab-item-link.default-hover-danger {
@apply hover:border-danger hover:text-danger;
}
.tab .tab-nav .tab-item .tab-item-link.default-primary {
@apply text-primary border-primary;
}
.tab .tab-nav .tab-item .tab-item-link.default-secondary {
@apply text-secondary border-secondary;
}
.tab .tab-nav .tab-item .tab-item-link.default-success {
@apply text-success border-success;
}
.tab .tab-nav .tab-item .tab-item-link.default-info {
@apply text-info border-info;
}
.tab .tab-nav .tab-item .tab-item-link.default-warning {
@apply text-warning border-warning;
}
.tab .tab-nav .tab-item .tab-item-link.default-danger {
@apply text-danger border-danger;
}
.tab .tab-nav .tab-item .tab-item-link.link-card {
@apply px-5 mx-1 text-white rounded-t-md;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-vertical {
@apply px-5 my-0 text-white rounded-bl-none rounded-t-md md:rounded-tr-none md:rounded-l-md;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-primary {
@apply bg-primary/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-secondary {
@apply bg-secondary/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-success {
@apply bg-success/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-info {
@apply bg-info/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-warning {
@apply bg-warning/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-danger {
@apply bg-danger/90;
}
.tab .tab-nav .tab-item .tab-item-link.link-card-primary-active {
@apply text-primary/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-card-secondary-active {
@apply text-secondary/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-card-success-active {
@apply text-success/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-card-info-active {
@apply text-info/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-card-warning-active {
@apply text-warning/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-card-danger-active {
@apply text-danger/90;
background-color: rgb(var(--bg-2));
}
.tab .tab-nav .tab-item .tab-item-link.link-justify-left {
@apply flex flex-wrap justify-start;
}
.tab .tab-nav .tab-item .tab-item-link.link-justify-center {
@apply flex flex-wrap justify-center;
}
.tab .tab-nav .tab-item .tab-item-link.link-justify-right {
@apply flex flex-wrap justify-end;
}
.tab .tab-content {
@apply rounded-lg;
background-color: rgb(var(--bg-2));
}
.tab .tab-content.content-vertical {
@apply flex-grow;
}
.tab .tab-content.content-vertical-fill {
@apply flex-1;
}
.tab .tab-content.content-border {
@apply border rounded-b-md;
}
.tab .tab-content.content-border-vertical {
@apply border rounded-md rounded-l-none;
}
.tab .tab-content.content-border-primary {
@apply border-primary;
}
.tab .tab-content.content-border-secondary {
@apply border-secondary;
}
.tab .tab-content.content-border-success {
@apply border-success;
}
.tab .tab-content.content-border-info {
@apply border-info;
}
.tab .tab-content.content-border-warning {
@apply border-warning;
}
.tab .tab-content.content-border-danger {
@apply border-danger;
}
.tab .tab-content .tab-pane {
@apply py-4 px-4;
}

View File

@ -0,0 +1,32 @@
/* Table Component */
.table-wrapper {
@apply w-full border-0 rounded-md border-gray-200 dark:border-slate-700;
}
.table-header {
@apply m-4 overflow-hidden;
transition: max-height 0.25s cubic-bezier(0, 1, 0, 1);
max-height: 42px;
}
.table-header.open {
@apply overflow-visible;
transition: max-height 0.5s ease-in-out;
max-height: 9999px;
}
.table-header-filter {
@apply flex flex-col md:flex-row justify-start md:justify-between items-start md:items-center gap-4;
}
.table-content {
@apply w-full;
}
.table-footer {
@apply flex justify-center md:justify-between items-center m-4;
}
.table-footer-page {
@apply flex justify-center gap-x-2;
}

View File

@ -0,0 +1,59 @@
.formkit-inner-box {
@apply relative;
}
.formkit-fieldset-box {
@apply max-w-md border border-[rgb(var(--fk-border-color))] rounded-lg px-4 py-2;
}
.formkit-legend-box {
@apply font-bold text-sm;
}
.formkit-wrapper-box {
@apply flex items-center mb-3 cursor-pointer;
}
.formkit-help-box {
@apply mb-3;
}
.formkit-input-box {
@apply flex
items-center
hover:cursor-pointer
appearance-none
h-5 w-5 mr-2
border-2
border-[rgb(var(--fk-border-color))]
checked:bg-primary
checked:border-transparent
bg-[rgb(var(--bg-2))]
rounded-md
checked:shadow-sm checked:shadow-primary/40
focus:outline-none focus:ring-0 transition duration-200;
}
.formkit-input-radio {
@apply flex
items-center
hover:cursor-pointer
appearance-none
h-5 w-5 mr-2
border-2
border-[rgb(var(--fk-border-color))]
checked:bg-primary
checked:border-transparent
bg-[rgb(var(--bg-2))]
rounded-full
checked:shadow-sm checked:shadow-primary/40
focus:outline-none focus:ring-0 transition duration-200;
}
.formkit-label-box {
@apply text-sm formkit-disabled:text-gray-300;
}
.formkit-message-box {
@apply formkit-invalid:text-red-500;
}

View File

@ -0,0 +1,7 @@
.formkit-wrapper-button {
@apply mb-1;
}
.formkit-input-button {
@apply bg-primary hover:bg-primary/90 text-white text-sm font-normal py-2 px-5 rounded-lg;
}

View File

@ -0,0 +1,18 @@
.formkit-label-color {
@apply block mb-1 font-bold text-sm;
}
.formkit-input-color {
@apply w-16 h-10 cursor-pointer rounded-lg mb-2 border-none appearance-none bg-transparent;
}
.formkit-input-color::-webkit-color-swatch {
border-radius: 5px;
border: none;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}
.formkit-input-color::-moz-color-swatch {
border-radius: 5px;
border: none;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}

View File

@ -0,0 +1,7 @@
.formkit-inner-dropzone {
@apply w-full;
}
.formkit-dropzone {
@apply border-2 border-[rgb(var(--fk-border-color))] border-dashed p-6 active:bg-[rgb(var(--bg-1))];
}

View File

@ -0,0 +1,32 @@
.formkit-label-file {
@apply block mb-1 font-bold text-sm;
}
.formkit-inner-file {
@apply w-full cursor-pointer;
}
.formkit-input-file {
@apply w-full cursor-pointer border rounded-lg text-gray-600 text-sm mb-1 file:cursor-pointer file:mr-4 file:py-2 file:px-4 file:rounded-l-lg file:border-0 file:text-sm file:bg-primary file:text-white hover:file:bg-primary/90;
border-color: rgb(var(--fk-border-color));
}
.formkit-file-list {
@apply flex flex-col;
}
.formkit-file-item {
@apply flex items-center py-2 px-4 rounded-lg border border-gray-200 mb-1 mt-1;
}
.formkit-file-name {
@apply text-[rgb(var(--text-color))] text-sm;
}
.formkit-file-remove {
@apply ml-auto text-primary text-sm;
}
.formkit-no-files {
@apply text-[rgb(var(--text-color))] text-sm;
}

View File

@ -0,0 +1,23 @@
.formkit-label-global {
@apply text-[rgb(var(--text-color))];
}
.formkit-outer-global {
@apply mb-4 text-[rgb(var(--text-color))] formkit-disabled:opacity-50;
}
.formkit-help-global {
@apply text-xs mt-1;
}
.formkit-messages-global {
@apply list-none p-0 mt-1 mb-0;
}
.formkit-message-global {
@apply text-red-500 mb-1 text-xs;
}
.formkit-wrapper-global {
@apply relative;
}

View File

@ -0,0 +1,29 @@
.formkit-label-otp {
@apply block mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger;
}
.formkit-inner-otp {
@apply flex
items-center
justify-start
align-middle
rounded-lg mb-1
overflow-hidden;
}
.formkit-digit-otp {
@apply w-10 h-10 mr-2
text-center
rounded-lg
border
border-[rgb(var(--fk-border-color))]
text-sm
bg-[rgb(var(--bg-2))]
placeholder-secondary
focus-within:border-primary
focus:outline-none;
}
.formkit-message-otp {
@apply formkit-invalid:text-red-500 dark:formkit-invalid:text-danger;
}

View File

@ -0,0 +1,3 @@
.formkit-input-range {
@apply appearance-none w-full h-2 p-0 bg-[rgb(var(--bg-1))] rounded-full focus:outline-none focus:ring-0 focus:shadow-none;
}

View File

@ -0,0 +1,43 @@
.formkit-outer-text {
@apply block mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger;
}
.formkit-inner-text {
@apply flex
items-center
justify-center
align-middle
w-full
border
border-[rgb(var(--fk-border-color))]
formkit-invalid:border-red-500
rounded-lg
overflow-hidden
focus-within:border-primary
mb-0;
}
.formkit-input-text {
@apply w-full
h-10
px-3
border-none
text-sm
bg-[rgb(var(--bg-2))]
placeholder-[rgb(var(--fk-placeholder-color))]
focus:outline-none
disabled:bg-[rgb(var(--bg-1))]
disabled:border-[rgb(var(--bg-1))]
disabled:placeholder-[rgb(var(--bg-1))]
read-only:bg-[rgb(var(--bg-1))]
read-only:border-[rgb(var(--bg-1))]
read-only:placeholder-[rgb(var(--bg-1))];
}
.formkit-prefix-text {
@apply ml-2;
}
.formkit-message-text {
@apply formkit-invalid:text-red-500;
}

View File

@ -0,0 +1,8 @@
.formkit-input-textarea {
@apply block
w-full
px-3 py-2
placeholder-[rgb(var(--fk-placeholder-color))]
bg-[rgb(var(--bg-2))]
focus:outline-none;
}

View File

@ -0,0 +1,33 @@
/* Base Import Tailwind CSS */
@import "tailwindcss/base";
@import "./base/theme";
/* Components Import Tailwind CSS */
@import "tailwindcss/components";
@import "./component/common";
@import "./component/alert";
@import "./component/badge";
@import "./component/button";
@import "./component/card";
@import "./component/collapse";
@import "./component/dropdown";
@import "./component/modal";
@import "./component/progress";
@import "./component/tab";
@import "./component/table";
/* Form Import Tailwind CSS */
@import "./form/global";
@import "./form/text";
@import "./form/textarea";
@import "./form/box";
@import "./form/button";
@import "./form/otp";
@import "./form/color";
@import "./form/file";
@import "./form/range";
@import "./form/dropzone";
/* Utilities Import Tailwind CSS */
@import "tailwindcss/utilities";
@import "./utility/typography";

View File

@ -0,0 +1,29 @@
h1 {
@apply text-4xl
font-bold;
}
h2 {
@apply text-3xl
font-bold;
}
h3 {
@apply text-2xl
font-bold;
}
h4 {
@apply text-xl
font-bold;
}
h5 {
@apply text-lg
font-bold;
}
h6 {
@apply text-base
font-bold;
}

View File

@ -0,0 +1,49 @@
$small: 640px;
$medium: 768px;
$large: 1024px;
$xlarge: 1280px;
.filter-wrapper {
&.filter-wrapper-show {
display: block;
}
&.filter-wrapper-hide {
display: none;
}
}
@media screen and (max-width: $medium) {
.filter-wrapper {
transition: right 0.25s ease;
&.filter-wrapper-show {
display: block;
right: 0px;
}
&.filter-wrapper-hide {
display: block;
right: -260px;
}
}
.filter-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: -50;
&.show {
visibility: visible;
opacity: 1;
}
&.hide {
visibility: hidden;
opacity: 0;
}
}
}

View File

@ -0,0 +1,18 @@
.h-layout {
.content-page {
padding: 10.5rem 2rem 0 2rem;
height: 100vh;
}
.w-header,
.w-header-search {
width: 100%;
}
.w-header-search {
position: fixed;
display: flex;
align-items: center;
height: 64px;
}
}

View File

@ -0,0 +1,107 @@
$small: 640px;
$medium: 768px;
$large: 1024px;
$xlarge: 1280px;
@media screen and (max-width: $medium) {
body:has(.menu-overlay:not(.hide)) {
overflow: hidden;
}
.menu-overlay {
position: fixed;
top: 0;
height: 120vh;
z-index: 45;
display: block;
visibility: visible;
opacity: 1;
transition: all 0.5s ease;
background-color: rgba(34, 41, 47, 0.5);
left: 0;
right: 0;
}
.menu-overlay {
&.hide {
visibility: hidden;
opacity: 0;
}
}
}
// Layout Vertical CSS
.v-layout {
.content-page {
height: 100%;
}
.w-header-search {
position: fixed;
display: flex;
align-items: center;
height: 64px;
}
.w-header,
.w-header-search {
width: calc(100% - 256px);
@media screen and (max-width: $medium) {
width: 100%;
}
}
.vertical-menu {
left: 0;
}
.content-page {
margin-left: 256px;
padding: 6rem 2rem 10px 2rem;
@media screen and (max-width: $medium) {
margin-left: 0px;
padding: 6rem 1.25rem 0 1.25rem;
}
}
&.menu-hide {
.w-header {
width: calc(100% - 0px);
}
.vertical-menu {
left: -260px;
}
.content-page {
margin-left: 0;
}
}
.menu-content,
.menu-content-child {
max-height: 1000px;
overflow: hidden;
&.hide {
max-height: 0px;
}
}
.menu-content-max,
.menu-content-child-max {
max-height: 100vh;
}
svg.side-menu-arrow {
transition: 0.25s ease-in-out;
}
.nav-open {
svg.side-menu-arrow {
transform: rotate(90deg);
}
}
}

View File

@ -0,0 +1,27 @@
$small: 640px;
$medium: 768px;
$large: 1024px;
$xlarge: 1280px;
.s-dropdown {
background-color: white;
padding: 10px 0;
border-radius: 5px;
top: 65px !important;
border: 1px solid rgb(229, 231, 235);
box-shadow: 0px 0px 10px rgba(226, 232, 240, 0.2);
@media screen and (max-width: $medium) {
left: 0px !important;
width: 100% !important;
margin: 0px 2px !important;
}
}
.dark {
.s-dropdown {
background-color: #1e293b;
box-shadow: 0px 0px 10px rgba(15, 23, 42, 0.2);
border: 1px solid #182130;
}
}

View File

@ -0,0 +1,19 @@
.v-popper--theme-dropdown .v-popper__inner {
background: rgb(var(--bg-2)) !important;
border: 0px !important;
color: rgb(var(--text-color)) !important;
}
// Disable arrow
.v-popper__arrow-container {
display: none;
}
// .v-popper--theme-my-theme .v-popper__inner {
// background: #fff000;
// color: black;
// padding: 24px;
// border-radius: 6px;
// border: 1px solid #ddd;
// box-shadow: 0 6px 30px rgba(0, 0, 0, 0.1);
// }

View File

@ -0,0 +1,237 @@
$small: 640px;
$medium: 768px;
$large: 1024px;
$xlarge: 1280px;
.formkit-form {
.form-wizard {
border-radius: 0.5rem;
}
&.top-form {
.formkit-actions {
button[type="submit"] {
display: block;
margin-right: 0;
margin-left: auto;
}
}
ul.top-steps {
display: flex;
justify-content: space-evenly;
margin-bottom: 1rem;
gap: 1rem;
@media screen and (max-width: $medium) {
flex-direction: column;
justify-content: start;
}
li {
flex-grow: 1;
display: inline-block;
.progress {
position: relative;
bottom: -8px;
width: 0%;
height: 8px;
border-radius: 10px;
background-color: rgb(var(--color-primary));
transition: width 0.25s ease-in;
}
.step--errors {
display: inline-flex;
justify-content: center;
align-items: center;
float: right;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #f23131;
color: #fff;
font-size: 12px;
}
.counter {
display: inline-block;
}
&:after {
content: "";
display: block;
width: 100%;
height: 8px;
border-radius: 10px;
background-color: rgb(var(--bg-1));
}
&[data-step-active="true"] {
color: rgb(var(--color-primary));
font-weight: 600;
.progress {
width: 100%;
}
}
&[data-step-active="false"] {
color: rgb(var(--bg-1));
.progress {
width: 0%;
}
}
&[data-step-completed="true"] {
color: rgb(var(--color-primary));
font-weight: 600;
.progress {
width: 100%;
}
}
}
}
}
&.left-form {
display: grid;
gap: 1rem;
grid-template-columns: 230px auto;
@media screen and (max-width: $medium) {
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 0rem;
}
.formkit-actions {
grid-column: span 2 / span 2;
@media screen and (max-width: $medium) {
grid-column: span 1 / span 1;
}
button[type="submit"] {
float: right;
}
}
ul.left-steps {
display: block;
li {
margin-bottom: 1rem;
.progress {
position: relative;
bottom: -8px;
width: 0%;
height: 8px;
border-radius: 10px;
background-color: rgb(var(--color-primary));
transition: width 0.25s ease-in;
}
.step--errors {
display: inline-flex;
justify-content: center;
align-items: center;
float: right;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #f23131;
color: #fff;
font-size: 12px;
}
.counter {
display: inline-block;
}
&:after {
content: "";
display: block;
width: 100%;
height: 8px;
border-radius: 10px;
background-color: rgb(var(--bg-1));
}
&[data-step-active="true"] {
color: rgb(var(--color-primary));
font-weight: 600;
.progress {
width: 100%;
}
}
&[data-step-active="false"] {
color: rgb(var(--bg-1));
.progress {
width: 0%;
}
}
&[data-step-completed="true"] {
color: rgb(var(--color-primary));
font-weight: 600;
.progress {
width: 100%;
}
}
}
}
}
}
// Custom Formkit Input for VSelect
.formkit-vselect {
margin-bottom: 1rem;
.vs__dropdown-menu {
background: rgb(var(--bg-2));
}
.vs__dropdown-menu li {
background: rgb(var(--bg-1));
color: rgb(var(--text-color));
}
.vs__dropdown-toggle {
min-height: 2.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
color: rgb(var(--color-primary));
border-width: 1px;
border-radius: 0.5rem;
margin-bottom: 0px;
border-color: rgb(var(--fk-border-color));
.vs__selected {
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
background-color: rgb(var(--color-primary));
color: white;
border: 0;
border-radius: 0.5rem;
gap: 0.25rem;
margin-right: 0.35rem;
margin-left: 0;
}
}
}
// Custom Decorator
input[type="checkbox"]:checked.icon-check + .formkit-decorator::before {
content: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>');
position: absolute;
top: -1px;
}

View File

@ -0,0 +1,114 @@
$small: 640px;
$medium: 768px;
$large: 1024px;
$xlarge: 1280px;
.fc-theme-standard td,
.fc-theme-standard th {
border-color: rgb(var(--bg-1)) !important;
}
// Overwrite fullcalendar styles light
.fc {
.fc-scrollgrid {
border: 1px solid rgb(var(--bg-1)) !important;
}
.fc-toolbar {
@media screen and (max-width: $medium) {
flex-direction: column;
align-items: flex-start !important;
gap: 10px;
}
.fc-toolbar-chunk {
display: flex;
align-items: center;
}
}
.fc-button {
padding: 0.75rem 1rem !important;
&.fc-button-primary {
background-color: transparent !important;
color: rgb(var(--color-primary));
border-color: rgb(var(--color-primary)) !important;
}
&.fc-button-active {
background-color: rgb(var(--color-primary)) !important;
}
&:hover {
background-color: rgb(var(--color-primary)) !important;
color: white !important;
}
&:focus {
box-shadow: 0 0 0 0.05rem rgba(255, 113, 133, 0.5) !important;
background-color: rgb(var(--color-primary)) !important;
color: white !important;
}
}
.fc-view-harness {
table {
&.fc-col-header {
td,
th {
border-color: rgb(var(--bg-1)) !important;
}
.fc-col-header-cell {
padding: 0.5rem;
}
}
}
.fc-daygrid-day {
&.fc-day-today {
background-color: rgb(var(--bg-1));
background: rgb(var(--bg-1));
}
}
.fc-daygrid-day-top {
color: #6b727f !important;
}
.fc-more-link {
color: rgb(var(--color-primary));
}
}
.fc-daygrid-event-harness {
.fc-daygrid-event {
padding: 0.5rem 1rem;
}
.fc-h-event {
&.fc-event-start {
border-color: rgb(var(--color-primary));
background-color: rgb(var(--color-primary));
}
}
}
.fc-list {
.fc-list-event-dot {
background-color: rgb(var(--color-primary));
border: 5px solid #f3f4f6;
border-color: rgb(var(--color-primary)) !important;
}
.fc-list-day-cushion {
background-color: #f3f4f6;
padding: 0.5rem 1rem;
}
}
.fc-popover-header {
padding: 0.5rem !important;
background: #f3f4f6;
}
}

View File

@ -0,0 +1,81 @@
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: #fb7185;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 3px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #fb7185, 0 0 5px #fb7185;
opacity: 1;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: #fb7185;
border-left-color: #fb7185;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,18 @@
.swiper-button-next,
.swiper-button-prev {
color: #4B5563 !important;
padding: 1.5rem;
}
.swiper-button-next:after,
.swiper-button-prev:after {
font-size: 1.5rem !important;
}
.swiper-pagination-bullet {
background-color: #8b98a9 !important;
}
.swiper-pagination-bullet-active {
background: #4B5563 !important;
}

View File

@ -0,0 +1,7 @@
.flag.normal-flag {
margin: 0em -1em -0em -1em !important;
transform: scale(0.38) !important;
-ms-transform: scale(0.38) !important;
-webkit-transform: scale(0.38) !important;
-moz-transform: scale(0.38) !important;
}

View File

@ -0,0 +1,3 @@
.Vue-Toastification__toast {
padding: 18px 21px;
}

View File

@ -0,0 +1,21 @@
/* width */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
/* Track */
::-webkit-scrollbar-track {
background-color: transparent;
}
/* Handle */
::-webkit-scrollbar-thumb {
background-color: rgb(var(--scroll-color));
border-radius: 6px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgb(var(--scroll-hover-color));
}

View File

@ -0,0 +1,12 @@
// Animation for transition fade
.fade-up-enter-active,
.fade-up-leave-active {
transition: transform 0.4s cubic-bezier(0.17, 0.67, 0.84, 0.79),
opacity 0.3s linear;
}
.fade-up-enter-from,
.fade-up-leave-to {
opacity: 0;
transform: translateY(-10%);
}

View File

@ -0,0 +1,10 @@
// Animation for transition fade
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

View File

@ -0,0 +1,11 @@
// Animation for transition fade
.page-enter-active,
.page-leave-active {
transition: transform 0.3s ease, opacity 0.4s ease;
}
.page-enter-from,
.page-leave-to {
transform: scale(1.02);
opacity: 0;
}

View File

@ -0,0 +1,17 @@
/*
Enter and leave animations can use different
durations and timing functions.
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}

View File

@ -0,0 +1,10 @@
.slide-enter-active,
.slide-leave-active {
transition: translateY, opacity 0.2s ease;
}
.slide-enter-from,
.slide-leave-to {
transform: translateY(0px);
opacity: 0;
}

View File

@ -0,0 +1,54 @@
/*================================================================================
Notes: This file is the main entry point for the SCSS stylesheet.
================================================================================*/
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap");
html,
body {
height: 100%;
}
#__nuxt {
font-family: "Space Grotesk", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 400;
font-size: 14px;
}
// svg.icon{
// width: 1.3rem;
// height: 1.3rem;
// vertical-align: middle;
// fill: currentColor;
// overflow: hidden;
// }
code {
color: rgb(233, 74, 74);
background-color: rgba(146, 146, 146, 0.1);
}
// Custom Layout SCSS
@import "./custom/layout/vertical";
@import "./custom/layout/horizontal";
// Transition SCSS
@import "./custom/transition/page";
@import "./custom/transition/fade";
@import "./custom/transition/fade-up";
@import "./custom/transition/slide";
@import "./custom/transition/slide-fade";
// Scrollbar SCSS
@import "./custom/scrollbar/scrollbar";
// Custom SCSS library
@import "./custom/library/dropdown";
@import "./custom/library/nprogress";
@import "./custom/library/formkit";
@import "./custom/library/vuetoastification";
@import "./custom/library/swiper";
@import "./custom/library/fullcalendar";
@import "./custom/library/floatingvue";
@import "./custom/library/vuecountryflag";

54
components/Loading.vue Normal file
View File

@ -0,0 +1,54 @@
<script setup>
const showMessage = ref(false);
setTimeout(() => {
showMessage.value = true;
}, 2000);
const refreshPage = () => {
// hard refresh
window.location.reload(true);
};
</script>
<template>
<div class="rs-loading bg-white absolute z-50 top-0 left-0 w-full h-full">
<div class="flex justify-center text-center items-center h-screen">
<div>
<div class="flex justify-center items-center">
<img
src="@/assets/img/logo/logo-nobg.svg"
class="w-[50px] mb-5"
alt=""
/>
</div>
<div
class="flex justify-center items-center"
aria-label="Loading..."
role="status"
>
<svg class="h-14 w-14 animate-spin" viewBox="3 3 18 18">
<path
class="fill-[#1F2C3A]/10"
d="M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"
></path>
<path
class="fill-[#1F2C3A]"
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
></path>
</svg>
</div>
<div v-if="showMessage" class="my-10 text-gray-500 font-medium">
If loading takes too long,
<br />
please click
<button @click="refreshPage">
<span class="text-[#F3586A]">here</span>
</button>
or hard refresh your browser.
</div>
</div>
</div>
</div>
</template>

243
components/RSCalendar.vue Normal file
View File

@ -0,0 +1,243 @@
<script setup>
import { DateTime } from "luxon";
const props = defineProps({
events: {
type: Array,
default: () => [],
},
});
const dateNow = ref(DateTime.now());
const arrDate = ref([]);
const dayInWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const monthInYear = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const label = (date) => {
// console.log(date.toString());
return date.toFormat("d");
};
const nextMonth = () => {
dateNow.value = DateTime.local(
dateNow.value.year,
dateNow.value.month,
dateNow.value.day
).plus({
months: 1,
});
};
const prevMonth = () => {
dateNow.value = DateTime.local(
dateNow.value.year,
dateNow.value.month,
dateNow.value.day
).minus({
months: 1,
});
};
const getDateInMonth = () => {
const date = DateTime.local(dateNow.value.year, dateNow.value.month);
const dayInMonth = date.daysInMonth;
arrDate.value = [];
for (let i = 0; i < dayInMonth; i++) {
const day = i + 1;
const date = DateTime.local(dateNow.value.year, dateNow.value.month, day);
const toDay = date.toFormat("ccc");
// Start of the Date
if (i == 0) {
const toDayIndex = dayInWeek.indexOf(toDay);
for (let i = 0; i < toDayIndex; i++) {
const dayBefore = toDayIndex - i;
const dateBefore = date.minus({ days: dayBefore });
updateArrDate(dateBefore, false, false);
}
if (DateTime.now().toISODate() == date.toISODate())
updateArrDate(date, true, true);
else updateArrDate(date, true, false);
}
// End of the Date
else if (i == dayInMonth - 1) {
const toDayIndex = dayInWeek.length - dayInWeek.indexOf(toDay) - 1;
if (DateTime.now().toISODate() == date.toISODate())
updateArrDate(date, true, true);
else updateArrDate(date, true, false);
for (let i = 0; i < toDayIndex; i++) {
const dayAfter = i + 1;
const dateAfter = date.plus({ days: dayAfter });
updateArrDate(dateAfter, false, false);
}
} else {
if (DateTime.now().toISODate() == date.toISODate())
updateArrDate(date, true, true);
else updateArrDate(date, true, false);
}
}
// console.log(arrDate.value);
};
const updateArrDate = (date, currentMonth, isToday) => {
arrDate.value.push({
date: date,
isCurrentMonth: currentMonth,
isToday: isToday,
event: checkEvent(date),
});
};
// Check props.event start and end date with date given
const checkEvent = (date) => {
let result = false;
props.events.forEach((event) => {
if (event.startDate) {
let startDate = "";
let endDate = "";
startDate = DateTime.fromISO(event.startDate);
if (event.endDate) {
endDate = DateTime.fromISO(event.endDate);
}
if (date.toISODate() == startDate.toISODate())
result = [
{
position: "start",
title: event.title,
},
];
else if (
date.toISODate() > startDate.toISODate() &&
date.toISODate() < endDate.toISODate()
)
result = [
{
position: "between",
title: event.title,
},
];
else if (date.toISODate() == endDate.toISODate())
result = [
{
position: "end",
title: event.title,
},
];
else result = false;
}
});
return result; // return result
};
onMounted(() => {
getDateInMonth();
});
watch(dateNow, () => {
getDateInMonth();
});
</script>
<template>
<div class="calendar">
<div class="calendar-header">
<!-- <div class="flex">1</div> -->
<div class="flex justify-between items-center my-4">
<h5>
{{ date.toFormat("LLLL yyyy") }}
</h5>
<div class="flex gap-5">
<button
@click="prevMonth"
class="flex items-center px-2 py-2 rounded-md shadow-md bg-white text-primary hover:bg-primary/80 hover:text-white"
>
<Icon size="20px" name="ic:round-keyboard-arrow-left"></Icon>
</button>
<button
@click="nextMonth"
class="flex items-center px-2 py-2 rounded-md shadow-md bg-white text-primary hover:bg-primary/80 hover:text-white"
>
<Icon size="20px" name="ic:round-keyboard-arrow-right"></Icon>
</button>
</div>
</div>
</div>
<div class="calendar-body rounded-md border border-primary/20">
<div class="calendar-body-header max-w-full">
<ul
class="grid grid-cols-7 list-none bg-primary/50 text-primary rounded-t-md"
>
<li
class="flex justify-center items-center p-5"
v-for="(val, index) in dayInWeek"
:key="index"
>
<span class="font-semibold text-base">{{ val }}</span>
</li>
</ul>
</div>
<div class="calendar-body-content">
<ul class="grid grid-cols-7 list-none">
<li
class="relative flex justify-center items-center h-30 border border-primary/10 whitespace-nowrap"
:class="{
'bg-primary/5': val.isToday,
}"
v-for="(val, index) in allDate"
:key="index"
>
<div class="flex-1">
<label
class="absolute top-2 right-3 font-semibold"
:class="{
'text-primary/20': !val.isCurrentMonth,
'text-primary/70': val.isCurrentMonth,
}"
for="day"
>{{ label(val.date) }}</label
>
<div class="event" v-if="val.event">
<div
class="font-semibold p-5 bg-primary text-white rounded-md"
style="min-height: 5rem"
:class="{
'rounded-r-none ml-5': event.position === 'start',
'rounded-r-none rounded-l-none':
event.position === 'between',
'rounded-l-none mr-5': event.position === 'end',
}"
v-for="(event, index) in val.event"
:key="index"
>
{{ event.position == "start" ? event.title : " " }}
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>

60
components/RsAlert.vue Normal file
View File

@ -0,0 +1,60 @@
<script setup>
const props = defineProps({
variant: {
type: String,
default: "primary",
},
dismissible: {
type: Boolean,
default: false,
},
show: {
type: Boolean,
default: true,
},
autoDismiss: {
type: Boolean,
default: false,
},
timer: {
type: Number,
default: 1000,
},
});
const showComponent = ref(props.show);
const autoDismiss = () => {
setTimeout(() => {
showComponent.value = false;
}, props.timer);
};
onMounted(() => {
if (props.autoDismiss) {
autoDismiss();
}
});
</script>
<template>
<Transition name="fade-up">
<div
v-if="showComponent"
class="alert"
:class="{
'alert-primary': variant === 'primary',
'alert-secondary': variant === 'secondary',
'alert-info': variant === 'info',
'alert-success': variant === 'success',
'alert-warning': variant === 'warning',
'alert-danger': variant === 'danger',
}"
>
<slot />
<button @click="showComponent = false">
<Icon name="ic:baseline-close" size="14"></Icon>
</button>
</div>
</Transition>
</template>

145
components/RsApiTester.vue Normal file
View File

@ -0,0 +1,145 @@
<script setup>
const props = defineProps({
url: {
type: String,
required: true,
},
});
const url = ref(window.location.origin + props.url);
const params = ref([
{
key: "",
value: "",
},
]);
const method = ref("POST");
const dropdownMethods = ref([
{
label: "GET",
value: "GET",
},
{
label: "POST",
value: "POST",
},
{
label: "PUT",
value: "PUT",
},
{
label: "DELETE",
value: "DELETE",
},
]);
const bodyJson = ref("{}");
const response = ref("");
watch(
params,
() => {
const urlParams = new URLSearchParams();
params.value.forEach((param) => {
if (param.key !== "") {
urlParams.append(param.key, param.value);
}
});
url.value = window.location.origin + props.url + "?" + urlParams.toString();
},
{ deep: true }
);
const addParam = () => {
params.value.push({
key: "",
value: "",
});
};
const removeParam = (index) => {
if (params.value.length === 1) return;
params.value.splice(index, 1);
};
const hitAPI = async () => {
const res = await useFetch(url.value, {
method: method.value,
initialCache: false,
body: JSON.stringify(bodyJson.value),
});
response.value = JSON.stringify(res.data.value, null, 2);
};
</script>
<template>
<div>
<FormKit type="text" label="URL" v-model="url" disabled />
<FormKit
type="select"
label="Method"
:options="dropdownMethods"
v-model="method"
/>
<label
class="formkit-label text-gray-700 dark:text-gray-200 mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger flex justify-between items-center"
for="input_8"
>Parameter
<rs-button size="sm" @click="addParam()"> Add</rs-button>
</label>
<div class="dynamic-params grid grid-cols-3 gap-4">
<div v-for="(val, index) in params">
<FormKit type="text" placeholder="Key" v-model="val.key" />
<FormKit type="text" placeholder="Value" v-model="val.value" />
<rs-button @click="removeParam(index)"> Remove</rs-button>
</div>
</div>
<br />
<label
class="formkit-label text-gray-700 dark:text-gray-200 mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger flex justify-between items-center"
for="input_8"
>Body
</label>
<ClientOnly>
<rs-code-mirror v-model="bodyJson" mode="application/json" height="300px">
</rs-code-mirror>
</ClientOnly>
<br />
<label
class="formkit-label text-gray-700 dark:text-gray-200 mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger flex justify-between items-center"
for="input_8"
>Response
</label>
<!-- <ClientOnly>
<rs-code-mirror v-model="response" height="300px" readonly="nocursor">
</rs-code-mirror>
</ClientOnly> -->
<FormKit
type="textarea"
:classes="{
input: '!bg-[#272822] text-white dark:bg-gray-800 dark:text-gray-200',
}"
rows="10"
v-model="response"
disabled
></FormKit>
<br />
<div class="flex justify-end">
<rs-button @click="hitAPI"> Test</rs-button>
</div>
</div>
</template>
<style lang="scss" scoped></style>

33
components/RsBadge.vue Normal file
View File

@ -0,0 +1,33 @@
<script setup>
const props = defineProps({
variant: {
type: String,
default: "primary",
},
icon: {
type: String,
default: "",
},
iconSize: {
type: String,
default: "18",
},
});
</script>
<template>
<div
class="badge"
:class="{
'badge-primary': variant === 'primary',
'badge-secondary': variant === 'secondary',
'badge-info': variant === 'info',
'badge-success': variant === 'success',
'badge-warning': variant === 'warning',
'badge-danger': variant === 'danger',
}"
>
<Icon v-if="icon" :name="icon" :size="iconSize"></Icon>
<slot />
</div>
</template>

53
components/RsButton.vue Normal file
View File

@ -0,0 +1,53 @@
<script setup>
const props = defineProps({
type: {
type: String,
default: "fill",
},
variant: {
type: String,
default: "primary",
},
size: {
type: String,
default: "md",
},
});
</script>
<template>
<button
class="button"
:class="{
'button-sm': size === 'sm',
'button-md': size === 'md',
'button-lg': size === 'lg',
// Filled Button
'button-primary': variant === 'primary',
'button-secondary': variant === 'secondary',
'button-info': variant === 'info',
'button-success': variant === 'success',
'button-warning': variant === 'warning',
'button-danger': variant === 'danger',
// Outline Button
'outline-primary': variant === 'primary-outline',
'outline-secondary': variant === 'secondary-outline',
'outline-info': variant === 'info-outline',
'outline-success': variant === 'success-outline',
'outline-warning': variant === 'warning-outline',
'outline-danger': variant === 'danger-outline',
//Text Button
'texts-primary': variant === 'primary-text',
'texts-secondary': variant === 'secondary-text',
'texts-info': variant === 'info-text',
'texts-success': variant === 'success-text',
'texts-warning': variant === 'warning-text',
'texts-danger': variant === 'danger-text',
}"
>
<slot />
</button>
</template>

16
components/RsCard.vue Normal file
View File

@ -0,0 +1,16 @@
<script setup></script>
<template>
<div class="card">
<header v-if="!!$slots.header" class="card-header">
<slot name="header" />
</header>
<main><slot></slot></main>
<div v-if="!!$slots.body" class="card-body">
<slot name="body" />
</div>
<footer v-if="!!$slots.footer" class="card-footer">
<slot name="footer" />
</footer>
</div>
</template>

223
components/RsCodeMirror.vue Normal file
View File

@ -0,0 +1,223 @@
<script setup>
import { useThemeStore } from "~/stores/theme";
import { vue } from "@codemirror/lang-vue";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";
import { amy, ayuLight, barf, clouds, cobalt, dracula } from "thememirror";
const props = defineProps({
options: {
type: Object,
default: () => ({}),
},
mode: {
type: String,
default: "vue",
},
height: {
type: String,
default: "70vh",
},
modelValue: {
type: String,
default: "",
},
theme: {
type: String,
default: "oneDark",
},
disabled: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["update:modelValue"]);
const themeStore = useThemeStore();
const editorTheme = ref(themeStore.codeTheme);
const dropdownThemes = ref([
{
label: "default",
value: "clouds",
},
{
label: "oneDark",
value: "oneDark",
},
{
label: "amy",
value: "amy",
},
{
label: "ayu",
value: "ayuLight",
},
{
label: "barf",
value: "barf",
},
{
label: "cobalt",
value: "cobalt",
},
{
label: "dracula",
value: "dracula",
},
]);
const value = ref(props.modelValue);
const extensions = ref([]);
if (props.mode == "vue") {
extensions.value = [vue(), oneDark];
} else {
extensions.value = [javascript(), oneDark];
}
const totalLines = ref(0);
const totalLength = ref(0);
// Codemirror EditorView instance ref
const view = shallowRef();
const handleReady = (payload) => {
view.value = payload.view;
totalLines.value = view.value.state.doc.lines;
totalLength.value = view.value.state.doc.length;
};
watch(
() => editorTheme.value,
(themeVal) => {
// themeStore.setCodeTheme(newValue.value);
if (props.mode == "vue") {
extensions.value = [
vue(),
themeVal === "oneDark"
? oneDark
: themeVal === "amy"
? amy
: themeVal === "ayuLight"
? ayuLight
: themeVal === "barf"
? barf
: themeVal === "cobalt"
? cobalt
: themeVal === "dracula"
? dracula
: clouds,
];
} else {
extensions.value = [
javascript(),
themeVal === "oneDark"
? oneDark
: themeVal === "amy"
? amy
: themeVal === "ayuLight"
? ayuLight
: themeVal === "barf"
? barf
: themeVal === "cobalt"
? cobalt
: themeVal === "dracula"
? dracula
: clouds,
];
}
}
);
// Status is available at all times via Codemirror EditorView
const getCodemirrorStates = () => {
const state = view.value.state;
const ranges = state.selection.ranges;
const selected = ranges.reduce((r, range) => r + range.to - range.from, 0);
const cursor = ranges[0].anchor;
const length = state.doc.length;
const lines = state.doc.lines;
console.log("state", view.value.state);
};
const onChange = (value) => {
// console.log("onChange", value);
emits("update:modelValue", value);
totalLines.value = view.value.state.doc.lines;
totalLength.value = view.value.state.doc.length;
};
const onFocus = (value) => {
// console.log("onFocus", value);
};
const onBlur = (value) => {
// console.log("onBlur", value);
};
const onUpdate = (value) => {
// console.log("onUpdate", value);
};
function numberComma(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
</script>
<template>
<div
class="flex justify-between items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf]"
>
<div class="flex items-center gap-2">
Theme:
<FormKit
v-model="editorTheme"
type="select"
placeholder="Select Themes"
:options="dropdownThemes"
:classes="{
input:
'!bg-[#282C34] !text-[#abb2bf] !border-[#abb2bf] hover:cursor-pointer h-6 w-[100px]',
inner: ' !rounded-none !mb-0',
outer: '!mb-0',
}"
/>
</div>
<!-- <rs-button
class="!p-2"
variant="primary-outline"
@click="getCodemirrorStates"
>
Get state</rs-button
> -->
</div>
<client-only>
<CodeMirror
v-model="value"
placeholder="Code goes here..."
:style="{ height: height }"
:autofocus="true"
:indent-with-tab="true"
:tab-size="2"
:extensions="extensions"
:disabled="disabled"
@ready="handleReady"
@change="onChange($event)"
@focus="onFocus($event)"
@blur="onBlur($event)"
@update="onUpdate($event)"
/>
</client-only>
<div
class="footer flex justify-end items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf]"
>
<span class="">Lines: {{ numberComma(totalLines) }}</span>
<span class="">Length: {{ numberComma(totalLength) }}</span>
</div>
</template>
<style lang="scss" scoped></style>

24
components/RsCollapse.vue Normal file
View File

@ -0,0 +1,24 @@
<script setup>
const props = defineProps({
accordion: {
type: Boolean,
default: false,
},
type: {
type: String,
default: "default",
},
});
</script>
<template>
<div
class="accordion"
:class="{
'accordion-border': type === 'border',
}"
v-uid
>
<slot />
</div>
</template>

View File

@ -0,0 +1,94 @@
<script setup>
const props = defineProps({
title: {
type: String,
required: false,
},
});
const collapseGroup = ref(null);
const parentID = ref(null);
const instance = getCurrentInstance();
const isAccordion = instance.parent.props.accordion;
const type = ref(instance.parent.props.type);
const height = ref(0);
const maxHeight = ref(60);
//watch intance type
watch(
() => instance.parent.props.type,
(newValue) => {
type.value = newValue;
},
{ deep: true }
);
const onClick = () => {
const parentElement = document.querySelector(`#${collapseGroup.value.id}`);
parentID.value = parentElement.parentElement.id;
const scrollHeight = parentElement.scrollHeight;
const targetOpenCollapse = parentElement.classList.contains(
"accordion-group--open"
);
const openCollapse = document.querySelector(
`#${parentID.value} .accordion-group--open`
);
if (isAccordion) {
if (openCollapse) {
const openCollapseHeader = document.querySelector(
`#${parentID.value} .accordion-group--open .accordion-header`
);
openCollapse.style.maxHeight = `${openCollapseHeader.scrollHeight}px`;
openCollapse.classList.remove("accordion-group--open");
}
}
if (targetOpenCollapse) {
parentElement.style.maxHeight = maxHeight.value + "px";
parentElement.classList.remove("accordion-group--open");
} else {
parentElement.style.maxHeight = scrollHeight + "px";
parentElement.classList.add("accordion-group--open");
}
};
// On mounted get height collapse header
onMounted(() => {
try {
const parentElement = document.querySelector(
`#${collapseGroup.value.id} .accordion-header`
);
const scrollHeight = parentElement.scrollHeight;
maxHeight.value = scrollHeight;
height.value = scrollHeight;
} catch (error) {
// console.log(error);
return;
}
});
</script>
<template>
<div
v-uid
ref="collapseGroup"
class="accordion-group"
:class="{
'accordion-default': type === 'default',
'accordion-border': type === 'border',
'accordion-card': type === 'card',
}"
:style="`max-height: ${maxHeight}px; transition-property: max-height`"
>
<div class="accordion-header" @click="onClick">
<slot v-if="!!$slots.title" name="title"></slot>
<span v-else> {{ title }}</span>
</div>
<div class="accordion-body">
<slot />
</div>
</div>
</template>

222
components/RsDropdown.vue Normal file
View File

@ -0,0 +1,222 @@
<script setup>
import { directive as vClickAway } from "vue3-click-away";
const props = defineProps({
title: {
type: String,
default: "Default",
},
variant: {
type: String,
default: "primary",
},
position: {
type: String,
default: "bottom",
},
textAlign: {
type: String,
default: "left",
},
size: {
type: String,
default: "md",
},
itemSize: {
type: String,
default: "10rem",
},
});
const isOpen = ref(false);
const dropdownRef = ref(null);
let originalPosition = null; // Store the original position of the dropdown
let lastKnownPosition = null; // Store the last known position of the dropdown
const toggle = (event) => {
isOpen.value = !isOpen.value;
};
const closeMenu = (event) => {
isOpen.value = false;
};
// Add a watcher for isOpen to reposition the dropdown when it's open
watch(isOpen, (newValue) => {
if (newValue) {
positionDropdown();
}
});
// Helper function to position the dropdown relative to the viewport
const positionDropdown = () => {
const dropdownElement = dropdownRef.value;
const dropdownSection = dropdownElement.querySelector(".dropdown-section");
if (!dropdownElement || !dropdownSection) return;
// Get the bounding rect of the dropdown and its section
const dropdownRect = dropdownElement.getBoundingClientRect();
const dropdownSectionRect = dropdownSection.getBoundingClientRect();
// Get the viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Check if the dropdown overflows the right or left side of the viewport
const rightOverflow =
dropdownRect.right + dropdownSectionRect.width - viewportWidth;
const leftOverflow = dropdownRect.left - dropdownSectionRect.width;
if (rightOverflow > 0) {
dropdownSection.style.right = "0";
dropdownSection.style.left = "unset";
} else if (leftOverflow < 0) {
dropdownSection.style.left = "0";
dropdownSection.style.right = "unset";
}
// Check if the dropdown overflows the bottom or top of the viewport
const bottomOverflow =
dropdownRect.bottom + dropdownSectionRect.height - viewportHeight;
const topOverflow = dropdownRect.top - dropdownSectionRect.height;
if (bottomOverflow > 0) {
dropdownSection.style.bottom = "100%";
dropdownSection.style.top = "unset";
} else if (topOverflow < 0) {
dropdownSection.style.top = "100%";
dropdownSection.style.bottom = "unset";
}
// Check if the position changed and update the lastKnownPosition
const newPosition = dropdownSection.getBoundingClientRect();
if (
!lastKnownPosition ||
JSON.stringify(lastKnownPosition) !== JSON.stringify(newPosition)
) {
lastKnownPosition = newPosition;
}
// Check if the dropdown is out of the viewport and reset its position to original
if (
isOpen.value &&
originalPosition &&
isOutOfViewport(dropdownSection, originalPosition)
) {
dropdownSection.style.top = originalPosition.top + "px";
dropdownSection.style.left = originalPosition.left + "px";
lastKnownPosition = originalPosition;
}
};
// Check if the element is out of the viewport
const isOutOfViewport = (element, position) => {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
return (
position.left < 0 ||
position.right > viewportWidth ||
position.top < 0 ||
position.bottom > viewportHeight
);
};
// Watch for viewport size changes (e.g., window resize) to reposition the dropdown
const handleResize = () => {
if (isOpen.value) {
positionDropdown();
}
};
// Watch for scrolling to reposition the dropdown
const handleScroll = () => {
if (isOpen.value) {
positionDropdown();
}
};
onMounted(() => {
// Add a listener for window resize to reposition the dropdown
window.addEventListener("resize", handleResize);
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
// Remove the window resize listener when the component is unmounted
window.removeEventListener("resize", handleResize);
window.removeEventListener("scroll", handleScroll);
});
</script>
<template>
<div class="dropdown" ref="dropdownRef" v-click-away="closeMenu">
<button
@click="toggle"
class="button"
:class="{
'button-sm': size === 'sm',
'button-md': size === 'md',
'button-lg': size === 'lg',
// Filled Button
'button-primary': variant === 'primary',
'button-secondary': variant === 'secondary',
'button-info': variant === 'info',
'button-success': variant === 'success',
'button-warning': variant === 'warning',
'button-danger': variant === 'danger',
// Outline Button
'outline-primary': variant === 'primary-outline',
'outline-secondary': variant === 'secondary-outline',
'outline-info': variant === 'info-outline',
'outline-success': variant === 'success-outline',
'outline-warning': variant === 'warning-outline',
'outline-danger': variant === 'danger-outline',
//Text Button
'texts-primary': variant === 'primary-text',
'texts-secondary': variant === 'secondary-text',
'texts-info': variant === 'info-text',
'texts-success': variant === 'success-text',
'texts-warning': variant === 'warning-text',
'texts-danger': variant === 'danger-text',
}"
type="button"
>
<slot v-if="$slots.title" name="title"></slot>
<span v-else>{{ props.title }}</span>
<Icon
v-if="position === 'bottom'"
name="ic:outline-keyboard-arrow-down"
/>
<Icon
v-else-if="position === 'top'"
name="ic:outline-keyboard-arrow-up"
/>
<Icon v-else-if="position === 'left'" name="ic:outline-chevron-left" />
<Icon v-else-if="position === 'right'" name="ic:outline-chevron-right" />
</button>
<section
class="dropdown-section"
:class="{
'list-bottom-sm': position == 'bottom' && size == 'sm',
'list-bottom-md': position == 'bottom' && size == 'md',
'list-bottom-lg': position == 'bottom' && size == 'lg',
'list-top-sm': position == 'top' && size == 'sm',
'list-top-md': position == 'top' && size == 'md',
'list-top-lg': position == 'top' && size == 'lg',
'list-left': position == 'left',
'list-right': position == 'right',
'list-align-right':
(position == 'bottom' || position == 'top') && textAlign == 'right',
}"
:style="`min-width: ${itemSize}`"
v-show="isOpen"
>
<slot></slot>
</section>
</div>
</template>

View File

@ -0,0 +1,21 @@
<script setup>
const emits = defineEmits(["click"]);
const props = defineProps({
divider: {
type: Boolean,
default: false,
},
});
const clickEvent = () => {
emits("click");
};
</script>
<template>
<hr v-if="divider" />
<div @click="clickEvent" class="dropdown-item">
<slot></slot>
</div>
</template>

45
components/RsFieldset.vue Normal file
View File

@ -0,0 +1,45 @@
<script setup>
// const props = defineProps({
// context: Object,
// });
// const label = props.context.label || "";
// const border = props.context.border || true;
// const borderColour = props.context.borderColour || "gray";
// const borderWidth = props.context.borderWidth || "1px";
// const borderRadius = props.context.borderRadius || "0.375rem";
const props = defineProps({
label: String,
border: {
type: Boolean,
default: true,
},
borderColour: {
type: String,
default: "rgb(226 232 240)",
},
borderWidth: {
type: String,
default: "1px",
},
borderRadius: {
type: String,
default: "0.375rem",
},
});
</script>
<template>
<fieldset
class="p-3"
:style="{
border: border ? '1px solid ' + borderColour : 'none',
borderRadius: borderRadius,
borderWidth: borderWidth,
}"
>
<legend class="font-bold text-sm">{{ label }}</legend>
<slot></slot>
</fieldset>
</template>

149
components/RsModal.vue Normal file
View File

@ -0,0 +1,149 @@
<script setup>
const emits = defineEmits(["update:modelValue"]);
const props = defineProps({
title: {
type: String,
default: "",
},
size: {
type: String,
default: "md",
},
dialogClass: {
type: String,
default: "",
},
modelValue: {
type: Boolean,
default: false,
},
position: {
type: String,
default: "top",
},
hideOverlay: {
type: Boolean,
default: false,
},
okOnly: {
type: Boolean,
default: false,
},
okTitle: {
type: String,
default: "OK",
},
cancelOnly: {
type: Boolean,
default: false,
},
cancelTitle: {
type: String,
default: "Cancel",
},
okCallback: {
type: Function,
default: () => {},
},
cancelCallback: {
type: Function,
default: () => {},
},
hideFooter: {
type: Boolean,
default: false,
},
overlayClose: {
type: Boolean,
default: true,
},
height: {
type: String,
default: "70vh",
},
});
const closeModal = () => {
emits("update:modelValue", false);
};
const validateCancelCallback = () => {
if (props.cancelCallback == "() => {}") closeModal();
else props.cancelCallback();
};
watch(
() => props.modelValue,
(val) => {
if (val) document.body.style.overflow = "hidden";
else document.body.style.overflow = "auto";
}
);
</script>
<template>
<Teleport to="body">
<transition-group name="fade">
<div
v-if="modelValue"
@click.self="overlayClose ? closeModal() : ''"
class="modal"
style="z-index: 1000"
:class="{
'modal-top': position == 'top',
'modal-center': position == 'center',
'modal-end': position == 'bottom',
'modal-hide-overlay': hideOverlay,
}"
>
<div
v-show="modelValue"
class="modal-dialog"
:class="dialogClass"
:style="{
width: size == 'sm' ? '300px' : size == 'md' ? '500px' : '800px',
}"
>
<div class="modal-content">
<div class="modal-header">
<h4 v-if="!$slots.header">
{{ title }}
</h4>
<slot name="header"></slot>
<Icon
@click="closeModal"
class="hover:text-gray-800 cursor-pointer"
name="ic:round-close"
></Icon>
</div>
<div class="modal-body">
<NuxtScrollbar
:style="{
'max-height': height,
}"
>
<slot name="body"></slot>
<slot v-if="!$slots.body"></slot>
</NuxtScrollbar>
</div>
<div v-if="!hideFooter" class="modal-footer">
<slot name="footer"></slot>
<rs-button
v-if="!$slots.footer && !okOnly"
@click="validateCancelCallback"
variant="primary-text"
>
{{ cancelTitle }}</rs-button
>
<rs-button
v-if="!$slots.footer && !cancelOnly"
@click="okCallback"
>{{ okTitle }}</rs-button
>
</div>
</div>
</div>
</div>
</transition-group>
</Teleport>
</template>

View File

@ -0,0 +1,63 @@
<script setup>
const props = defineProps({
label: {
type: String,
default: "",
},
value: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
variant: {
type: String,
default: "primary",
},
size: {
type: String,
default: "md",
},
showValue: {
type: Boolean,
default: false,
},
});
</script>
<template>
<div class="progress-wrapper">
<div class="progress-label">{{ label }}</div>
<div
class="progress"
:class="{
'progress-sm': size === 'sm',
'progress-md': size === 'md',
'progress-lg': size === 'lg',
'progress-primary': variant == 'primary',
'progress-secondary': variant == 'secondary',
'progress-success': variant == 'info',
'progress-info': variant == 'success',
'progress-warning': variant == 'warning',
'progress-danger': variant == 'danger',
}"
>
<div
class="progress-bar"
:class="{
primary: variant == 'primary',
secondary: variant == 'secondary',
info: variant == 'info',
success: variant == 'success',
warning: variant == 'warning',
danger: variant == 'danger',
}"
:style="{ width: (value / max) * 100 + '%' }"
>
<span class="text-xs" v-if="showValue">{{ value }}</span>
</div>
</div>
</div>
</template>

230
components/RsTab.vue Normal file
View File

@ -0,0 +1,230 @@
<script setup>
const props = defineProps({
variant: {
type: String,
default: "primary",
},
type: {
type: String,
default: "default",
},
vertical: {
type: Boolean,
default: false,
},
fill: {
type: Boolean,
default: false,
},
justify: {
type: String,
default: "left",
},
});
// Slots
const slots = useSlots();
const tabs = ref(slots.default().map((tab) => tab.props));
const selectedTitle = ref(tabs.value[0]["title"]);
tabs.value.forEach((tab) => {
if (typeof tab.active !== "undefined") {
selectedTitle.value = tab.title;
}
});
provide("selectedTitle", selectedTitle);
</script>
<template>
<client-only>
<div
class="tab"
:class="{
vertical: vertical,
'tab-card': type === 'card' && !vertical,
'card-vertical': type === 'card' && vertical,
'card-primary': type === 'card' && variant === 'primary',
'card-secondary': type === 'card' && variant === 'secondary',
'card-success': type === 'card' && variant === 'success',
'card-danger': type === 'card' && variant === 'danger',
'card-warning': type === 'card' && variant === 'warning',
'card-info': type === 'card' && variant === 'info',
}"
>
<ul
class="tab-nav"
:class="{
'tab-nav-card': type === 'card' && !vertical,
'tab-nav-card card-vertical': type === 'card' && vertical,
vertical: vertical,
'vertical-fill': vertical && fill,
}"
>
<li
class="tab-item"
:class="{
fill: fill,
border: type === 'border',
'border-horizontal': type === 'border' && !vertical,
'border-horizontal-active':
selectedTitle === val.title && type === 'border' && !vertical,
'border-vertical': type === 'border' && vertical,
'border-vertical-active':
selectedTitle === val.title && type === 'border' && vertical,
// Variant Color for Border Type
'border-hover-primary': type === 'border' && variant == 'primary',
'border-hover-secondary':
type === 'border' && variant == 'secondary',
'border-hover-info': type === 'border' && variant == 'info',
'border-hover-success': type === 'border' && variant == 'success',
'border-hover-warning': type === 'border' && variant == 'warning',
'border-hover-danger': type === 'border' && variant == 'danger',
// Variant Color for Border Type Active
'border-active-primary':
selectedTitle === val.title &&
type === 'border' &&
variant == 'primary',
'border-active-secondary':
selectedTitle === val.title &&
type === 'border' &&
variant == 'secondary',
'border-active-info':
selectedTitle === val.title &&
type === 'border' &&
variant == 'info',
'border-active-success':
selectedTitle === val.title &&
type === 'border' &&
variant == 'success',
'border-active-warning':
selectedTitle === val.title &&
type === 'border' &&
variant == 'warning',
'border-active-danger':
selectedTitle === val.title &&
type === 'border' &&
variant == 'danger',
}"
role="presentation"
v-for="(val, index) in tabs"
:key="index"
@click="selectedTitle = val.title"
>
<a
class="tab-item-link"
:class="{
default: type === 'default' && !vertical,
'default-vertical': type === 'default' && vertical,
'default-active':
selectedTitle === val.title && type === 'default' && !vertical,
'default-vertical-active':
selectedTitle === val.title && type === 'default' && vertical,
// Variant hover for default type
'default-hover-primary':
type === 'default' && variant == 'primary',
'default-hover-secondary':
type === 'default' && variant == 'secondary',
'default-hover-info': type === 'default' && variant == 'info',
'default-hover-success':
type === 'default' && variant == 'success',
'default-hover-warning':
type === 'default' && variant == 'warning',
'default-hover-danger': type === 'default' && variant == 'danger',
// Variant Color for default type Active
'default-primary':
selectedTitle === val.title &&
type === 'default' &&
variant == 'primary',
'default-secondary':
selectedTitle === val.title &&
type === 'default' &&
variant == 'secondary',
'default-info':
selectedTitle === val.title &&
type === 'default' &&
variant == 'info',
'default-success':
selectedTitle === val.title &&
type === 'default' &&
variant == 'success',
'default-warning':
selectedTitle === val.title &&
type === 'default' &&
variant == 'warning',
'default-danger':
selectedTitle === val.title &&
type === 'default' &&
variant == 'danger',
'link-card': type === 'card' && !vertical,
'link-card-vertical': type === 'card' && vertical,
// Variant Color for card type
'link-card-primary': type === 'card' && variant == 'primary',
'link-card-secondary': type === 'card' && variant == 'secondary',
'link-card-info': type === 'card' && variant == 'info',
'link-card-success': type === 'card' && variant == 'success',
'link-card-warning': type === 'card' && variant == 'warning',
'link-card-danger': type === 'card' && variant == 'danger',
// Variant Color for card type Active
'link-card-primary-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'primary',
'link-card-secondary-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'secondary',
'link-card-info-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'info',
'link-card-success-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'success',
'link-card-warning-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'warning',
'link-card-danger-active':
selectedTitle === val.title &&
type === 'card' &&
variant == 'danger',
'link-justify-left': justify == 'left',
'link-justify-center': justify == 'center',
'link-justify-right': justify == 'right',
}"
>{{ val.title }}</a
>
</li>
</ul>
<div
class="tab-content"
:class="{
'content-vertical': vertical && !fill,
'content-vertical-fill': vertical && fill,
'content-border': type === 'border' && !vertical,
'content-border-vertical': type === 'border' && vertical,
'content-border-primary': type === 'border' && variant === 'primary',
'content-border-secondary':
type === 'border' && variant === 'secondary',
'content-border-info': type === 'border' && variant === 'info',
'content-border-success': type === 'border' && variant === 'success',
'content-border-warning': type === 'border' && variant === 'warning',
'content-border-danger': type === 'border' && variant === 'danger',
}"
>
<slot></slot>
</div>
</div>
</client-only>
</template>

20
components/RsTabItem.vue Normal file
View File

@ -0,0 +1,20 @@
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
active: {
type: Boolean,
default: false,
},
});
const selectedTitle = inject("selectedTitle");
</script>
<template>
<div class="tab-pane" v-if="selectedTitle === title">
<slot></slot>
</div>
</template>

780
components/RsTable.vue Normal file
View File

@ -0,0 +1,780 @@
<script setup>
import { useLayoutStore } from "~/stores/layout";
import { useWindowSize } from "vue-window-size";
const layoutStore = useLayoutStore();
const mobileWidth = layoutStore.mobileWidth;
const { width } = useWindowSize();
const windowWidth = ref(width);
const props = defineProps({
field: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
basic: {
type: Boolean,
default: true,
},
advanced: {
type: Boolean,
default: false,
},
options: {
type: Object,
default: () => ({
variant: "default",
striped: false,
bordered: false,
borderless: false,
hover: false,
}),
},
optionsAdvanced: {
type: Object,
default: () => ({
sortable: true,
filterable: true,
responsive: false,
outsideBorder: false,
}),
},
grid: {
type: Boolean,
default: false,
},
pageSize: {
type: Number,
default: 5,
},
sort: {
type: Object,
default: () => ({
column: "",
direction: "asc",
}),
},
});
// Default varaiable
const columnTitle = ref([]);
const dataTable = ref(props.data);
const dataTitle = ref([]);
const dataLength = ref(props.data.length);
// Advanced Option Variable
const currentSort = ref(0);
const currentSortDir = ref("asc");
const currentPage = ref(1);
const pageSize = ref(props.pageSize);
const maxPageShown = ref(3);
// Searching Variable
const keyword = ref("");
// Filtering Variable
const filter = ref([]);
const openFilter = ref(false);
const hideTable = ref(false);
// Other Variable
const sortColumnFirstTime = ref(false);
const isDesktop = computed(() => {
return windowWidth.value >= mobileWidth ? true : false;
});
if (props.optionsAdvanced.responsive) {
if (isDesktop.value) {
hideTable.value = false;
} else {
hideTable.value = true;
}
}
const camelCasetoTitle = (str, exclusions = []) => {
if (exclusions.includes(str)) {
return str.replace(/([A-Z])/g, " $1").trim();
} else if (/\(.*\)/.test(str)) {
return str; // if the string contains parentheses, return the original string
} else {
return str.replace(/([A-Z])/g, " $1").replace(/^./, (str) => {
return str.toUpperCase();
});
}
};
const spacingCharactertoCamelCase = (array) => {
// Loop array string and convert to camel case
let result = [];
array.forEach((element) => {
if (element.charAt(0) == element.charAt(0).toUpperCase()) {
// Camelcase the string and remove spacing
// and if there is () in the string, do Uppercase inside the () and dont spacing it
let camelCase = element
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => {
return str.toUpperCase();
})
.replace(/\s/g, "");
let resultCamelCase = camelCase.replace(/\(([^)]+)\)/, (str) => {
return str.toUpperCase();
});
result.push(resultCamelCase);
} else {
result.push(element);
}
});
// console.log(result);
return result;
};
// watch props.data change and redo all the data
watch(
() => [props.data, props.field],
() => {
if (props.field && props.field.length > 0) {
columnTitle.value = spacingCharactertoCamelCase(props.field);
dataTitle.value = spacingCharactertoCamelCase(props.field);
} else {
columnTitle.value = Object.keys(dataTable.value[0]);
dataTitle.value = Object.keys(dataTable.value[0]);
}
},
{ immediate: true }
);
const setColumnTitle = (data) => {
try {
if (props.field && props.field.length == 0) {
columnTitle.value = Object.keys(data);
} else {
columnTitle.value = spacingCharactertoCamelCase(props.field);
}
} catch (error) {
console.log(error);
}
};
const filteredDatabyTitle = (data, title) => {
let result = "";
try {
if (props.field && props.field.length == 0) {
Object.entries(data).forEach(([key, value]) => {
if (key === title) {
result = value;
return;
}
});
} else {
// Get index title from columnTitle
let index = columnTitle.value.indexOf(title);
// Convert data json to array
let arr = Object.values(data);
result = arr[index];
}
if (result === "" || result === null) result = "-";
return result;
} catch (error) {
console.log(error);
return "-";
}
};
onMounted(() => {
setColumnTitle(dataTable.value[0]);
});
// Computed data
const computedData = computed(() => {
let result = [];
let totalData = 0;
result = dataTable.value
.slice()
.sort((a, b) => {
let modifier = 1;
columnTitle.value.forEach((title, index) => {
// console.log(title, props.sort.column);
// First sort by column title
if (title === props.sort.column && !sortColumnFirstTime.value) {
currentSort.value = index;
currentSortDir.value = props.sort.direction;
sortColumnFirstTime.value = true;
}
});
// Check if column title is number or string and convert spacing to camelcase
let a1 = filteredDatabyTitle(a, columnTitle.value[currentSort.value]);
let b1 = filteredDatabyTitle(b, columnTitle.value[currentSort.value]);
if (typeof a1 === "string") a1 = a1.toLowerCase();
if (typeof b1 === "string") b1 = b1.toLowerCase();
// Convert string to number if possible
if (isNumeric(a1)) a1 = parseFloat(a1);
if (isNumeric(b1)) b1 = parseFloat(b1);
if (currentSortDir.value === "desc") modifier = -1;
if (a1 < b1) return -1 * modifier;
if (a1 > b1) return 1 * modifier;
return 0;
})
.filter((row) => {
// Search all json object if keyword not equal null
if (keyword.value === "") return true;
let result = false;
Object.entries(row).forEach(([key, value]) => {
try {
if (
value.toString().toLowerCase().includes(keyword.value.toLowerCase())
) {
result = true;
currentPage.value = 1;
}
} catch (error) {
result = false;
}
});
return result;
})
.filter((_, index) => {
let start = (currentPage.value - 1) * pageSize.value;
let end = currentPage.value * pageSize.value;
totalData++;
if (index >= start && index < end) return true;
});
dataLength.value = totalData;
return result;
});
const isNumeric = (n) => {
return !isNaN(parseFloat(n)) && isFinite(n);
};
const totalEntries = computed(() => {
return dataLength.value;
});
const sort = (index) => {
if (index === currentSort.value) {
currentSortDir.value = currentSortDir.value === "asc" ? "desc" : "asc";
} else if (index !== currentSort.value && currentSortDir.value == "desc") {
currentSortDir.value = "asc";
}
currentSort.value = index;
};
const pages = computed(() => {
let totalPG = Math.ceil(dataLength.value / pageSize.value);
const numShown = Math.min(maxPageShown.value, totalPG);
let first = currentPage.value - Math.floor(numShown / 2);
first = Math.max(first, 1);
first = Math.min(first, totalPG - numShown + 1);
return [...Array(numShown)].map((k, i) => i + first);
});
const totalPage = computed(() => {
return Math.ceil(dataLength.value / pageSize.value);
});
const pageChange = (page) => {
currentPage.value = page;
};
const nextPage = () => {
if (currentPage.value * pageSize.value < dataLength.value)
currentPage.value++;
};
const prevPage = () => {
if (currentPage.value > 1) currentPage.value--;
};
const firstPage = () => {
currentPage.value = 1;
};
const lastPage = () => {
currentPage.value = totalPage.value;
};
const hideColumn = (key) => {
if (!getFilter(key)) {
// insert into filter variable to tell there is a change in filter
setFilter(key, "hide", true);
} else {
// update filter variable to tell there is a change in filter
setFilter(key, "hide", false);
}
};
const setFilter = (key, action, condition) => {
// Check if key exist inside filter
let index = filter.value.findIndex((item) => item.key === key);
if (index == -1) {
// If key not exist, insert new filter
filter.value.push({
key: key,
action: {
[action]: condition,
},
});
} else {
// If key exist, update filter
filter.value[index].action[action] = condition;
// console.log(filter.value);
}
};
const getFilter = (key) => {
let result = false;
filter.value.forEach((item) => {
if (item.key === key) {
result = item.action.hide;
}
});
return result;
};
// Watch filter.value
watch(
() => filter.value,
() => {
// console.log(filter.value);
// Loop json object filter.value
filter.value.forEach((item) => {
// Hide Column
if (item.action.hide) {
// Get index title from columnTitle
let index = columnTitle.value.indexOf(item.key);
if (index !== -1) {
// Remove column from columnTitle
columnTitle.value.splice(index, 1);
}
} else if (!item.action.hide) {
// Get index title from dataTitle
let indexData = dataTitle.value.indexOf(item.key);
if (!columnTitle.value.includes(item.key)) {
// Add Column back to its original position
columnTitle.value.splice(indexData, 0, item.key);
// Sort the columnTitle like dataTitle
columnTitle.value.sort((a, b) => {
let indexA = dataTitle.value.indexOf(a);
let indexB = dataTitle.value.indexOf(b);
return indexA - indexB;
});
}
}
});
},
{ deep: true }
);
const filterComputed = computed(() => {
let result = [];
let i = 0;
filter.value.forEach((item) => {
if (item.action.hide) {
result.push({
title: item.key,
hide: item.action.hide,
});
}
i++;
});
// console.log(result);
return result;
});
// watch pinia getter windowWidth
watch(
() => windowWidth.value,
() => {
if (props.optionsAdvanced.responsive) {
if (windowWidth.value <= mobileWidth) {
hideTable.value = true;
} else {
hideTable.value = false;
}
}
},
{ deep: true }
);
</script>
<template>
<div
v-if="data && data.length > 0 && dataTable && dataTable.length > 0"
class="table-wrapper"
:class="{
'!border': advanced && !hideTable && optionsAdvanced.outsideBorder,
}"
>
<div
class="table-header"
:class="{
open: openFilter,
'!max-h-full': !optionsAdvanced.filterable,
}"
v-if="advanced"
>
<div
class="table-header-filter"
:class="{
'!items-center !gap-3': !optionsAdvanced.filterable,
}"
>
<div>
<div class="flex gap-x-2">
<FormKit
v-model="keyword"
type="search"
placeholder="Search..."
outer-class="mb-0"
/>
<rs-button
v-if="optionsAdvanced.filterable"
class="!px-3 sm:!px-6"
@click="openFilter ? (openFilter = false) : (openFilter = true)"
>
<Icon
name="ic:outline-filter-alt"
class="mr-0 md:mr-1"
size="1rem"
/>
<span class="hidden sm:block">Filter</span>
</rs-button>
</div>
<!-- <rs-button class="mt-2">asdaasd</rs-button> -->
</div>
<div class="flex justify-center items-center gap-x-2">
<span class="text-[rgb(var(--text-color))]">Result per page:</span>
<FormKit
type="select"
v-model="pageSize"
:options="[5, 10, 25, 100]"
outer-class="mb-0"
/>
<!-- <v-select
:options="[5, 10, 25, 100]"
v-model="pageSize"
:clearable="false"
></v-select> -->
</div>
</div>
<div
class="flex flex-wrap items-center justify-start gap-x-3"
v-if="optionsAdvanced.filterable"
>
<rs-dropdown
:title="camelCasetoTitle(val)"
size="sm"
class="mt-3"
v-for="(val, index) in dataTitle"
:key="index"
>
<rs-dropdown-item @click="hideColumn(val)">
{{ getFilter(val) ? "Show Column" : "Hide Column" }}
<Icon
:name="getFilter(val) ? 'mdi:eye-outline' : 'mdi:eye-off-outline'"
size="1rem"
class="ml-auto"
></Icon>
</rs-dropdown-item>
</rs-dropdown>
</div>
</div>
<div
v-if="filterComputed.length > 0"
class="table-header-filter-list w-full m-4"
>
<div class="flex flex-wrap items-center justify-start gap-x-2">
<div
class="flex items-center justify-center gap-x-2 border border-primary text-primary rounded-lg py-1 px-2"
v-for="(val, index) in filterComputed"
:key="index"
>
{{ val ? camelCasetoTitle(val.title) : "" }}
<Icon
name="ic:round-close"
class="mr-0 md:mr-1 hover:text-red-500 cursor-pointer"
size="1rem"
@click="hideColumn(val.title)"
></Icon>
</div>
</div>
</div>
<div class="w-full overflow-x-auto">
<client-only>
<table
v-if="!hideTable"
class="table-content"
:class="{
'!border-y !border-0 border-[rgb(var(--bg-1))]': advanced,
'table-fixed': options.fixed,
'table-auto': !options.fixed,
}"
>
<thead
class="text-left border-[rgb(var(--border-color))]"
:class="{
'border-y': !options.borderless,
'border-[rgb(var(--border-color))] bg-[rgb(var(--bg-2))]':
options.variant === 'default',
'border-primary/50 bg-primary text-white':
options.variant === 'primary',
'border-secondary/50 bg-secondary text-white':
options.variant === 'secondary',
'border-info/50 bg-info text-white ': options.variant === 'info',
'border-success/50 bg-success text-white':
options.variant === 'success',
'border-warning/50 bg-warning text-white':
options.variant === 'warning',
'border-danger/50 bg-danger text-white':
options.variant === 'danger',
}"
>
<tr>
<th
class="relative py-3 pl-5 pr-8 whitespace-nowrap"
:class="{
'border-r last:border-l last:border-r-0':
options.bordered && !options.borderless,
'border-[rgb(var(--border-color))]':
options.variant === 'default',
'border-primary/80': options.variant === 'primary',
'border-secondary/80': options.variant === 'secondary',
'border-info/80': options.variant === 'info',
'border-success/80': options.variant === 'success',
'border-warning/80': options.variant === 'warning',
'border-danger/80': options.variant === 'danger',
'w-36': options.fixed,
'cursor-pointer': optionsAdvanced.sortable && advanced,
}"
style="min-width: 100px"
@click="
optionsAdvanced.sortable && advanced ? sort(index) : null
"
v-for="(val, index) in columnTitle"
:key="index"
>
{{ camelCasetoTitle(val) }}
<div
v-if="optionsAdvanced.sortable && advanced"
class="sortable"
>
<Icon
class="absolute top-3 right-2 opacity-20"
size="1.25rem"
name="carbon:chevron-sort"
/>
<Icon
v-if="currentSort == index && currentSortDir == 'asc'"
class="absolute top-3 right-2 opacity-50"
size="1.25rem"
name="carbon:chevron-sort-up"
/>
<Icon
v-else-if="currentSort == index && currentSortDir == 'desc'"
class="absolute top-3 right-2 opacity-50"
size="1.25rem"
name="carbon:chevron-sort-down"
/>
</div>
</th>
</tr>
</thead>
<tbody>
<tr
:class="{
'border-y border-[rgb(var(--border-color))]':
!options.bordered && !options.borderless,
'border-b': options.bordered && !options.borderless,
'border-b-0': options.borderless,
'border-[rgb(var(--border-color))] odd:bg-[rgb(var(--bg-1))] even:bg-[rgb(var(--bg-2))]':
options.variant === 'default' && options.striped,
'border-primary/20 odd:bg-white even:bg-primary/5':
options.variant === 'primary' && options.striped,
'border-secondary/20 odd:bg-white even:bg-secondary/5':
options.variant === 'secondary' && options.striped,
'border-info/20 odd:bg-white even:bg-info/5':
options.variant === 'info' && options.striped,
'border-success/20 odd:bg-white even:bg-success/5':
options.variant === 'success' && options.striped,
'border-warning/20 odd:bg-white even:bg-warning/5':
options.variant === 'warning' && options.striped,
'border-danger/20 odd:bg-white even:bg-danger/5':
options.variant === 'danger' && options.striped,
'cursor-pointer hover:bg-[rgb(var(--text-color))]':
options.hover && options.variant === 'default',
'cursor-pointer hover:bg-primary/5':
options.hover && options.variant === 'primary',
'cursor-pointer hover:bg-secondary/5':
options.hover && options.variant === 'secondary',
'cursor-pointer hover:bg-info/5':
options.hover && options.variant === 'info',
'cursor-pointer hover:bg-success/5':
options.hover && options.variant === 'success',
'cursor-pointer hover:bg-warning/5':
options.hover && options.variant === 'warning',
'cursor-pointer hover:bg-danger/5':
options.hover && options.variant === 'danger',
}"
v-for="(val1, index1) in computedData"
:key="index1"
>
<td
class="p-4 pl-5 break-words"
:class="{
'border-r last:border-l last:border-r-0':
options.bordered && !options.borderless,
'border-[rgb(var(--border-color))]':
options.variant === 'default',
'border-primary/20': options.variant === 'primary',
'border-secondary/20': options.variant === 'secondary',
'border-info/20': options.variant === 'info',
'border-success/20': options.variant === 'success',
'border-warning/20': options.variant === 'warning',
'border-danger/20': options.variant === 'danger',
}"
v-for="(val2, index2) in columnTitle"
:key="index2"
>
<slot
:name="val2"
:text="filteredDatabyTitle(val1, val2)"
:value="val1"
>
{{ filteredDatabyTitle(val1, val2) }}
</slot>
</td>
</tr>
</tbody>
</table>
<div v-else>
<rs-collapse v-if="computedData.length > 0" accordion>
<rs-collapse-item v-for="(val, index) in computedData" :key="index">
<template #title>
<div class="grid grid-cols-2">
<div class="flex flex-col col-span-1">
<span class="font-semibold leading-tight">
{{ Object.values(val)[0] }}
</span>
<span class="text-sm"> {{ Object.values(val)[1] }} </span>
</div>
<div class="flex justify-end items-center col-span-1">
<div class="mr-4">
{{ Object.values(val)[computedData.length] }}
</div>
</div>
</div>
</template>
<template #default>
<div
class="flex justify-between items-center even:bg-inherit odd:bg-[rgb(var(--bg-1))] rounded-lg p-3"
v-for="(val1, index1) in Object.entries(val).slice(
2,
Object.entries(val).length
)"
:key="index1"
>
<span>
{{ camelCasetoTitle(val1[0]) }}
</span>
<span>
{{ val1[1] }}
</span>
</div>
</template>
</rs-collapse-item>
</rs-collapse>
</div>
</client-only>
</div>
<div v-if="advanced" class="table-footer">
<div class="flex justify-center items-center gap-x-2">
<span class="text-sm text-[rgb(var(--text-color))] hidden md:block"
>Showing {{ pageSize * currentPage - pageSize + 1 }} to
{{ pageSize * currentPage }} of {{ totalEntries }} entries</span
>
</div>
<div class="table-footer-page">
<rs-button
:variant="`${
options.variant == 'default' ? 'primary' : options.variant
}-outline`"
class="!rounded-full !p-1 !w-8 !h-8"
@click="firstPage"
:disabled="currentPage == 1"
>
<Icon name="ic:round-keyboard-double-arrow-left" size="1rem"></Icon>
</rs-button>
<rs-button
:variant="`${
options.variant == 'default' ? 'primary' : options.variant
}-outline`"
class="!rounded-full !p-1 !w-8 !h-8"
@click="prevPage"
:disabled="currentPage == 1"
>
<Icon name="ic:round-keyboard-arrow-left" size="1rem"></Icon>
</rs-button>
<rs-button
:variant="`${
currentPage == val && options.variant != 'default'
? options.variant
: currentPage == val && options.variant == 'default'
? 'primary'
: options.variant == 'default'
? 'primary-outline'
: options.variant + '-outline'
}`"
class="!rounded-full !p-1 !w-8 !h-8"
v-for="(val, index) in pages"
:key="index"
@click="pageChange(val)"
>
{{ val }}
</rs-button>
<rs-button
:variant="`${
options.variant == 'default' ? 'primary' : options.variant
}-outline`"
class="!rounded-full !p-1 !w-8 !h-8"
@click="nextPage"
:disabled="currentPage == totalPage"
>
<Icon name="ic:round-keyboard-arrow-right" size="1rem"></Icon>
</rs-button>
<rs-button
:variant="`${
options.variant == 'default' ? 'primary' : options.variant
}-outline`"
class="!rounded-full !p-1 !w-8 !h-8"
@click="lastPage"
:disabled="currentPage == totalPage"
>
<Icon name="ic:round-keyboard-double-arrow-right" size="1rem"></Icon>
</rs-button>
</div>
</div>
</div>
</template>

228
components/RsWizard.vue Normal file
View File

@ -0,0 +1,228 @@
<script setup>
import { getNode, createMessage } from "@formkit/core";
const props = defineProps({
type: {
type: String,
default: "top",
},
steps: {
type: Array,
default: () => ["Default"],
},
currentStep: {
type: String,
default: "",
},
form: {
type: Boolean,
default: false,
},
formSubmit: {
type: Function,
default: () => {},
},
formAction: {
type: Boolean,
default: true,
},
formStepRequired: {
type: Boolean,
default: true,
},
formStepBack: {
type: Boolean,
default: false,
},
formNavigate: {
type: Boolean,
default: true,
},
formErrorCounter: {
type: Boolean,
default: true,
},
});
const step = reactive({});
const activeStep = ref(props.currentStep);
const stepNames = ref(props.steps);
const visitedSteps = ref([]);
const toLowerCase = (str) => str.toLowerCase().replace(/\s/g, "");
const stepIndex = (stepName) => stepNames.value.indexOf(stepName);
watch(activeStep, (newStep, oldStep) => {
if (oldStep && !visitedSteps.value.includes(oldStep)) {
visitedSteps.value.push(oldStep);
}
// NEW: trigger showing validation on fields
// within all visited steps
visitedSteps.value.forEach((step) => {
const node = getNode(step);
if (node != undefined)
node.walk((n) => {
n.store.set(
createMessage({
key: "submitted",
value: true,
visible: false,
})
);
});
});
});
const nextStep = (stepName) => {
const stepNames = Object.keys(step);
const currentIndex = stepNames.indexOf(activeStep.value);
const nextIndex = stepNames.indexOf(stepName);
if (props.formStepRequired) {
if (!props.formStepBack) {
if (nextIndex > currentIndex) {
if (step[activeStep.value].valid) {
activeStep.value = stepName;
} else {
const node = getNode(activeStep.value);
if (node)
node.walk((n) => {
n.store.set(
createMessage({
key: "submitted",
value: true,
visible: false,
})
);
});
}
}
} else {
if (step[activeStep.value].valid || currentIndex > nextIndex) {
activeStep.value = stepName;
} else {
const node = getNode(activeStep.value);
if (node)
node.walk((n) => {
n.store.set(
createMessage({
key: "submitted",
value: true,
visible: false,
})
);
});
}
}
} else {
activeStep.value = stepName;
}
};
const setStep = (delta) => {
const stepNames = Object.keys(step);
const currentIndex = stepNames.indexOf(activeStep.value);
nextStep(stepNames[currentIndex + delta]);
};
const stepPlugin = (node) => {
if (node.props.type == "group") {
// builds an object of the top-level groups
step[node.name] = step[node.name] || {};
node.on("created", () => {
// use 'on created' to ensure context object is available
step[node.name].valid = toRef(node.context.state, "valid");
});
// listen for changes in error count and store it
node.on("count:errors", ({ payload: count }) => {
step[node.name].errorCount = count;
});
// listen for changes in count of blocking validations messages
node.on("count:blocking", ({ payload: count }) => {
step[node.name].blockingCount = count;
});
// set the active tab to the 1st tab
if (activeStep.value === "") {
activeStep.value = node.name;
}
// Stop plugin inheritance to descendant nodes
return false;
}
};
const checkStepValidity = (stepName) => {
if (step[stepName]) {
return (
(step[stepName].errorCount > 0 || step[stepName].blockingCount > 0) &&
visitedSteps.value.includes(stepName)
);
}
};
</script>
<template>
<FormKit
type="form"
:plugins="[stepPlugin]"
:actions="formAction ? true : false"
@submit="formSubmit ? formSubmit() : ''"
:form-class="{ 'top-form': type == 'top', 'left-form': type == 'left' }"
>
<ul :class="{ 'top-steps': type == 'top', 'left-steps': type == 'left' }">
<li
v-for="(stepName, index) in stepNames"
:key="index"
:class="['step', { 'has-errors': checkStepValidity(stepName) }]"
@click="nextStep(stepName)"
:data-step-active="activeStep === stepName"
:data-step-completed="stepIndex(stepName) < stepIndex(activeStep)"
>
<div class="counter">{{ index + 1 }}.</div>
{{ stepName }}
<span
v-show="formErrorCounter"
v-if="checkStepValidity(stepName)"
class="step--errors"
v-text="step[stepName].errorCount + step[stepName].blockingCount"
/>
<div class="progress"></div>
</li>
</ul>
<div class="form-wizard">
<section
v-for="(stepName, index) in stepNames"
:key="index"
v-show="activeStep === stepName"
>
<FormKit type="group" :id="stepName" :name="stepName">
<slot
:name="stepName === 'Default' ? 'default' : toLowerCase(stepName)"
>
</slot>
</FormKit>
</section>
</div>
<div v-if="formNavigate" class="flex justify-between">
<FormKit
type="button"
:disabled="stepIndex(activeStep) == 0"
@click="setStep(-1)"
v-text="'Previous step'"
/>
<FormKit
type="button"
:disabled="stepIndex(activeStep) == stepNames.length - 1"
class="next"
@click="setStep(1)"
v-text="'Next step'"
/>
</div>
</FormKit>
</template>

View File

@ -0,0 +1,33 @@
<script setup>
import DraggableNested from "~/components/draggable/nested.vue";
const props = defineProps({
tasks: {
required: true,
type: Array,
},
});
</script>
<template>
<draggable
class="dragArea"
tag="ul"
:list="tasks"
:group="{ name: 'g1' }"
item-key="name"
>
<template #item="{ element }">
<li>
<p>{{ element.name }}</p>
<DraggableNested :tasks="element.tasks" />
</li>
</template>
</draggable>
</template>
<style scoped>
.dragArea {
min-height: 50px;
outline: 1px dashed;
}
</style>

View File

@ -0,0 +1,533 @@
<script setup>
import DraggableSideMenuNested from "~/components/draggable/sideMenuNested.vue";
const props = defineProps({
menus: {
required: true,
type: Array,
},
count: {
required: false,
default: 0,
type: Number,
},
parentMenu: {
required: false,
default: [],
type: Array,
},
});
const emits = defineEmits(["changeSideMenu"]);
const showModal = ref(false);
const type = ref(null);
const formMenu = ref({
index: null,
name: null,
title: null,
path: null,
icon: null,
});
const formHeader = ref({
index: null,
header: null,
description: null,
});
const viewPermissionType = ref([
{
label: "All",
value: "all",
},
{
label: "User",
value: "user",
},
{
label: "Role",
value: "role",
},
]);
const viewPermissionTypeRadio = ref("");
const roleList = ref([]);
const userList = ref([]);
const selectListValue = ref([]);
const checkAll = ref(false);
// watch viewPermissionTypeRadio
watch(
viewPermissionTypeRadio,
async (val) => {
if (val == "") viewPermissionTypeRadio.value = "all";
else if (val == "user") await getUserList();
else if (val == "role") await getRoleList();
// Check if selectListValue doesnt match with user or role list then reset selectListValue
if (val == "user") {
selectListValue.value = selectListValue.value.filter((item) => {
return userList.value.some((user) => user.value == item.value);
});
} else if (val == "role") {
selectListValue.value = selectListValue.value.filter((item) => {
return roleList.value.some((role) => role.value == item.value);
});
}
checkAll.value = false;
},
{ immediate: true }
);
// watch checkAll
watch(
checkAll,
(val) => {
if (val) {
if (viewPermissionTypeRadio.value == "user") {
selectListValue.value = userList.value.map((user) => {
return {
label: user.label,
value: user.value,
};
});
} else if (viewPermissionTypeRadio.value == "role") {
selectListValue.value = roleList.value.map((role) => {
return {
label: role.label,
value: role.value,
};
});
}
} else {
selectListValue.value = [];
}
},
{ immediate: true }
);
const getUserList = async () => {
const { data } = await useFetch("/api/devtool/menu/user-list");
if (data.value?.statusCode === 200) {
userList.value = data.value.data.map((user) => {
return {
label: user.userUsername,
value: user.userUsername,
};
});
}
};
const getRoleList = async () => {
const { data } = await useFetch("/api/devtool/menu/role-list");
if (data.value?.statusCode === 200) {
roleList.value = data.value.data.map((role) => {
return {
label: role.roleName,
value: role.roleName,
};
});
}
};
const clone = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
// Modal functions
const openModal = () => {
showModal.value = true;
};
const assignDataMenu = (menu) => {
formMenu.value = {
index: menu.index,
name: menu.name,
title: menu.title,
path: menu.path,
icon: menu.icon,
};
if (menu.meta?.auth?.user) {
viewPermissionTypeRadio.value = "user";
selectListValue.value = menu.meta.auth.user.map((user) => {
return {
label: user,
value: user,
};
});
} else if (menu.meta?.auth?.role) {
viewPermissionTypeRadio.value = "role";
selectListValue.value = menu.meta.auth.role.map((role) => {
return {
label: role,
value: role,
};
});
} else {
viewPermissionTypeRadio.value = "all";
}
};
const assignDataHeader = (header) => {
formHeader.value = {
index: props.menus.indexOf(header),
header: header.header,
description: header.description,
};
if (header.meta?.auth?.user) {
viewPermissionTypeRadio.value = "user";
selectListValue.value = header.meta.auth.user.map((user) => {
return {
label: user,
value: user,
};
});
} else if (header.meta?.auth?.role) {
viewPermissionTypeRadio.value = "role";
selectListValue.value = header.meta.auth.role.map((role) => {
return {
label: role,
value: role,
};
});
} else {
viewPermissionTypeRadio.value = "all";
}
};
const clickOK = () => {
showModal.value = false;
};
const clickCancel = () => {
showModal.value = false;
};
// Update the menus
const updateMenus = (menus) => {
emits("changeSideMenu", menus);
};
// Save the menu
const saveEditChanges = () => {
let newMenu = props.menus;
if (type.value == "menu") {
// Overwrite the props menus
props.menus.map((menu) => {
if (menu.path == formMenu.value.path) {
menu.title = formMenu.value.title;
menu.icon = formMenu.value.icon;
menu.meta = {};
// Add the meta auth based on viewPermissionTypeRadio
if (viewPermissionTypeRadio.value == "user") {
menu.meta.auth = {
user: selectListValue.value.map((user) => {
return user.value;
}),
};
} else if (viewPermissionTypeRadio.value == "role") {
menu.meta.auth = {
role: selectListValue.value.map((role) => {
return role.value;
}),
};
}
}
});
newMenu = props.parentMenu;
} else if (type.value == "header") {
// Overwrite the props menus
newMenu = props.menus.map((header, index) => {
if (index == formHeader.value.index) {
header.header = formHeader.value.header;
header.description = formHeader.value.description;
header.meta = {};
// Add the meta auth based on viewPermissionTypeRadio
if (viewPermissionTypeRadio.value == "user") {
header.meta.auth = {
user: selectListValue.value.map((user) => {
return user.value;
}),
};
} else if (viewPermissionTypeRadio.value == "role") {
header.meta.auth = {
role: selectListValue.value.map((role) => {
return role.value;
}),
};
}
}
return header;
});
}
// Update the menus
updateMenus(newMenu);
showModal.value = false;
};
const removeChild = (type, data) => {
// console.log(data);
// console.log(type);
let newMenu = props.menus;
if (type == "menu") {
let parentMenu = props.parentMenu;
// Overwrite the props menus
newMenu = props.menus.filter((menu) => {
return menu.path == data;
});
// Remove the newMenu from the parentMenu child
parentMenu = parentMenu.filter((menu) => {
// Level 1
if (menu.child) {
menu.child.forEach((el) => {
// Level 2
if (el.child) {
el.child = el.child.filter((child) => {
return child.path != data;
});
}
if (el.path == data) {
menu.child.splice(menu.child.indexOf(el), 1);
}
});
}
return menu;
});
newMenu = parentMenu;
} else if (type == "header") {
// Remove the object array from the props menus
newMenu = props.menus.filter((header, index) => {
return index != data;
});
}
// Update the menus
updateMenus(newMenu);
};
</script>
<template>
<div>
<draggable
class="dragArea"
tag="div"
:list="menus"
:group="{ name: 'menu', put: props.count == 0 ? false : true }"
:clone="clone"
item-key="id"
>
<template #item="{ element }">
<rs-card
class="p-4 !my-4 mx-0 mb-0 relative border-2 border-[rgb(var(--border-color))]"
:class="{
'py-6': count > 0,
}"
>
<div class="flex justify-between items-center">
<div class="text-left font-normal text-xs mb-2">
<span class="uppercase text-primary dark:text-primary">{{
count == 0 && element.header
? element.header
: count === 0
? "(No Header)"
: ""
}}</span>
<p class="text-gray-500 dark:text-gray-500">
{{
count == 0 && element.description
? element.description
: count === 0
? "There will be no header shown"
: ""
}}
</p>
</div>
<div v-if="count == 0">
<Icon
name="material-symbols:edit-outline-rounded"
class="text-primary hover:text-primary/90 cursor-pointer"
size="20"
@click="
type = 'header';
assignDataHeader(element);
openModal();
"
></Icon>
<Icon
name="material-symbols:close-rounded"
class="text-primary hover:text-primary/90 cursor-pointer"
size="20"
@click="removeChild('header', menus.indexOf(element))"
></Icon>
</div>
</div>
<div class="flex justify-between items-center">
<p class="flex items-center gap-2">
<Icon v-if="element.icon" :name="element.icon" size="22"></Icon>
{{ element.title }}
</p>
<div v-if="count > 0">
<Icon
name="material-symbols:edit-outline-rounded"
class="text-primary hover:text-primary/90 cursor-pointer"
size="20"
@click="
type = 'menu';
assignDataMenu(element);
openModal();
"
></Icon>
<Icon
name="material-symbols:close-rounded"
class="text-primary hover:text-primary/90 cursor-pointer"
size="20"
@click="removeChild('menu', element.path)"
></Icon>
</div>
</div>
<div v-if="element?.meta?.auth" class="authuser-wrapper mt-3">
<div class="flex">
<div v-for="(val, index) in element.meta.auth.user">
<rs-badge
v-if="index < 5"
variant="danger"
class="mr-1 text-sm"
>
{{ val }}
</rs-badge>
</div>
</div>
<div class="flex">
<div v-for="(val, index) in element.meta.auth.role">
<rs-badge
v-if="index < 5"
variant="warning"
class="mr-1 text-sm"
>
{{ val }}
</rs-badge>
</div>
</div>
</div>
<DraggableSideMenuNested
:menus="element?.child ? element.child : []"
:count="count + 1"
:parentMenu="
props.parentMenu && props.parentMenu.length > 0
? props.parentMenu
: props.menus
"
@changeSideMenu="updateMenus"
/>
</rs-card>
</template>
</draggable>
<rs-modal
:title="type == 'header' ? 'Edit Header' : 'Edit Menu'"
v-model="showModal"
ok-title="Confirm"
:ok-callback="saveEditChanges"
:cancel-callback="clickCancel"
>
<div v-if="type == 'header'">
<FormKit
type="hidden"
label="Index"
v-model="formHeader.index"
></FormKit>
<FormKit type="text" label="Name" v-model="formHeader.header"></FormKit>
<FormKit
type="text"
label="Description"
v-model="formHeader.description"
></FormKit>
</div>
<div v-else-if="type == 'menu'">
<FormKit type="text" label="Title" v-model="formMenu.title"></FormKit>
<FormKit
type="text"
label="Path"
v-model="formMenu.path"
readonly
></FormKit>
<FormKit type="text" label="Icon" v-model="formMenu.icon"></FormKit>
<div class="mb-4 text-sm">
<p class="font-semibold mb-2">
Preview Icon (<a
href="https://icones.js.org/collection/all"
class="text-primary hover:underline"
target="_blank"
>https://icones.js.org/collection/all</a
>)
</p>
<Icon v-if="formMenu.icon" :name="formMenu.icon"></Icon>
</div>
</div>
<hr class="mb-4" />
<h4 class="text-semibold mb-4">Menu Permission</h4>
<FormKit
type="radio"
label="View Type"
v-model="viewPermissionTypeRadio"
:classes="{
fieldset: 'border-0 !p-0',
legend: '!font-semibold !text-sm mb-0',
options: '!flex !flex-row gap-4 mt-3',
}"
:options="viewPermissionType"
/>
<div
v-if="viewPermissionTypeRadio && viewPermissionTypeRadio != 'all'"
class="form-wrapper"
>
<div class="flex justify-between items-center mb-2">
<label
class="formkit-label flex items-center gap-x-4 font-semibold text-gray-700 dark:text-gray-200 blockfont-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger"
for="input_4"
>
{{ viewPermissionTypeRadio == "user" ? "User" : "Role" }}
</label>
</div>
<v-select
class="formkit-vselect"
:options="
viewPermissionTypeRadio == 'user'
? userList
: viewPermissionTypeRadio == 'role'
? roleList
: []
"
v-model="selectListValue"
multiple
></v-select>
<FormKit
type="checkbox"
v-model="checkAll"
:label="
viewPermissionTypeRadio == 'user'
? 'Check All User'
: 'Check All Role'
"
input-class="icon-check"
/>
</div>
</rs-modal>
</div>
</template>

View File

@ -0,0 +1,13 @@
<script setup>
const props = defineProps({
context: Object,
});
function handleInput(e) {
props.context.node.input(e.target.value);
}
</script>
<template>
<input @input="handleInput" :value="props.context._value" />
</template>

View File

@ -0,0 +1,139 @@
<script setup>
/* eslint-disable */
import { useDropzone } from "vue3-dropzone";
const props = defineProps({
context: Object,
});
const fileBase64 = ref([]);
const files = ref([]);
let err = ref(false);
let errmsg = ref("");
const accept = props.context.accept;
const multiple = props.context.multiple;
const maxSize = props.context.maxSize;
const minSize = props.context.minSize;
const maxFiles = props.context.maxFiles;
const disabled = props.context.disabled;
const toBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
async function onDrop(fileList, fileError, event) {
if (fileError.length == 0) {
err.value = false;
errmsg.value = "";
for (let i = 0; i < fileList.length; i++) {
const base64 = await toBase64(fileList[i]);
fileBase64.value.push({ data: fileList[i], base64 });
files.value.push([fileList[i]]);
}
} else {
err.value = true;
errmsg.value = fileError[0].errors[0].message;
}
updateNodeValue();
}
async function removeFiles(index) {
fileBase64.value.splice(index, 1);
files.value.splice(index, 1);
updateNodeValue();
}
function updateNodeValue() {
props.context.node.input(files.value);
}
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept,
multiple: multiple === "true" ? true : false,
maxSize: maxSize ? parseInt(maxSize) : Infinity,
minSize: minSize ? parseInt(minSize) : 0,
maxFiles: maxFiles ? parseInt(maxFiles) : 0,
disabled: disabled === "true" ? true : false,
});
</script>
<template>
<!-- eslint-disable -->
<div :class="context.classes.dropzone">
<div v-bind="getRootProps()" class="cursor-pointer">
<input v-bind="getInputProps()" />
<div class="flex items-center justify-center h-36">
<div>
<Icon
class="!block m-auto mb-3"
size="30px"
name="ic:outline-upload-file"
/>
<p class="text-center" v-if="isDragActive">Drop the files here ...</p>
<p v-else>Drop files or click here to upload files</p>
</div>
</div>
</div>
<div
id="fileList"
class="grid sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4"
v-auto-animate
>
<div
v-for="(file, index) in fileBase64"
class="relative overflow-hidden w-full h-20 md:h-36 rounded-lg border-2 border-[rgb(var(--border-color))]"
v-auto-animate
>
<img
v-if="file.data.type.includes('image')"
:src="file.base64"
class="w-full h-20 md:h-36 object-cover object-center rounded-lg"
/>
<div
v-else
class="h-full flex items-center justify-center opacity-50 text-primary font-semibold uppercase text-xl whitespace-nowrap"
>
{{
file.data.name.slice(
((file.data.name.lastIndexOf(".") - 1) >>> 0) + 2
)
}}
</div>
<Icon
name="ic:round-close"
@click="removeFiles(index)"
class="cursor-pointer absolute top-1 right-1 text-[rgb(var(--text-color))] bg-[rgb(var(--bg-2))] p-1 rounded-full"
size="18"
/>
<div
class="absolute bottom-1 right-1 bg-[rgb(var(--bg-2))] px-2 rounded-lg"
>
<span class="font-semibold text-xs text-[rgb(var(--text-color))]">
{{ file.data.path }}
</span>
</div>
</div>
</div>
<ul
v-if="err"
class="formkit-messages list-none p-0 mt-1 mb-0 relative -bottom-5 -left-2"
aria-live="polite"
>
<li
class="formkit-message text-red-500 mb-1 text-xs formkit-invalid:text-red-500 dark:formkit-invalid:text-danger"
id="input_9-rule_required"
data-message-type="validation"
>
{{ errmsg }}
</li>
</ul>
</div>
</template>

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