all
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1679
package-lock.json
generated
Normal file
1679
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/forms": "^0.5.4",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.26",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: { config: './src/css/tailwind.config.js' },
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
8
src/App.vue
Normal file
8
src/App.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import './charts/ChartjsConfig';
|
||||
</script>
|
||||
|
||||
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
38
src/components/AccordionBasic.vue
Normal file
38
src/components/AccordionBasic.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="px-5 py-4 rounded-sm dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
|
||||
<button
|
||||
class="flex items-center justify-between w-full group mb-1"
|
||||
@click.prevent="open = !open"
|
||||
:aria-expanded="open"
|
||||
>
|
||||
<div class="text-sm text-slate-800 dark:text-slate-100 font-medium">{{ title }}</div>
|
||||
<svg class="w-8 h-8 shrink-0 fill-current text-slate-400 dark:text-slate-500 group-hover:text-slate-500 dark:group-hover:text-slate-400 ml-3" :class="{ 'rotate-180': open }" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="text-sm" v-show="open">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'AccordionBasic',
|
||||
props: ['title'],
|
||||
setup() {
|
||||
|
||||
const open = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
return {
|
||||
open,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
src/components/AccordionTableItem.vue
Normal file
80
src/components/AccordionTableItem.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<tbody class="text-sm">
|
||||
<tr>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center text-slate-800">
|
||||
<div class="w-10 h-10 shrink-0 flex items-center justify-center bg-slate-100 dark:bg-slate-700 rounded-full mr-2 sm:mr-3">
|
||||
<img class="rounded-full ml-1" :src="item.image" width="40" height="40" :alt="item.customer" />
|
||||
</div>
|
||||
<div class="font-medium text-slate-800 dark:text-slate-100">{{item.customer}}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left font-medium text-emerald-500">{{item.total}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="inline-flex font-medium bg-amber-100 dark:bg-amber-400/30 text-amber-600 dark:text-amber-400 rounded-full text-center px-2.5 py-0.5">{{item.status}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-center">{{item.items}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left">{{item.location}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 shrink-0 mr-2" viewBox="0 0 16 16">
|
||||
<path d="M4.3 4.5c1.9-1.9 5.1-1.9 7 0 .7.7 1.2 1.7 1.4 2.7l2-.3c-.2-1.5-.9-2.8-1.9-3.8C10.1.4 5.7.4 2.9 3.1L.7.9 0 7.3l6.4-.7-2.1-2.1zM15.6 8.7l-6.4.7 2.1 2.1c-1.9 1.9-5.1 1.9-7 0-.7-.7-1.2-1.7-1.4-2.7l-2 .3c.2 1.5.9 2.8 1.9 3.8 1.4 1.4 3.1 2 4.9 2 1.8 0 3.6-.7 4.9-2l2.2 2.2.8-6.4z" />
|
||||
</svg>
|
||||
<div>{{item.type}}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div class="flex items-center">
|
||||
<button class="text-slate-400 hover:text-slate-500 dark:text-slate-500 dark:hover:text-slate-400 transform" :class="{ 'rotate-180': descriptionOpen }" @click.prevent="descriptionOpen = !descriptionOpen" :aria-expanded="descriptionOpen" :aria-controls="`description-${item.id}`">
|
||||
<span class="sr-only">Menu</span>
|
||||
<svg class="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!--
|
||||
Example of content revealing when clicking the button on the right side:
|
||||
Note that you must set a "colspan" attribute on the <td> element,
|
||||
and it should match the number of columns in your table
|
||||
-->
|
||||
<tr :id="`description-${item.id}`" role="region" :class="!descriptionOpen && 'hidden'">
|
||||
<td colspan="10" class="px-2 first:pl-5 last:pr-5 py-3">
|
||||
<div class="flex items-center bg-slate-50 dark:bg-slate-900/30 dark:text-slate-400 p-3 -mt-3">
|
||||
<svg class="w-4 h-4 shrink-0 fill-current text-slate-400 dark:text-slate-500 mr-2">
|
||||
<path d="M1 16h3c.3 0 .5-.1.7-.3l11-11c.4-.4.4-1 0-1.4l-3-3c-.4-.4-1-.4-1.4 0l-11 11c-.2.2-.3.4-.3.7v3c0 .6.4 1 1 1zm1-3.6l10-10L13.6 4l-10 10H2v-1.6z" />
|
||||
</svg>
|
||||
<div class="italic">{{item.description}}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'AccordionTableItem',
|
||||
props: ['item'],
|
||||
setup() {
|
||||
|
||||
const descriptionOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
return {
|
||||
descriptionOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
73
src/components/AccordionTableRichItem.vue
Normal file
73
src/components/AccordionTableRichItem.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<tbody class="text-sm">
|
||||
<tr>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center text-slate-800">
|
||||
<div class="w-10 h-10 shrink-0 flex items-center justify-center bg-slate-100 dark:bg-slate-700 rounded-full mr-2 sm:mr-3">
|
||||
<img class="rounded-full ml-1" :src="item.image" width="40" height="40" :alt="item.customer" />
|
||||
</div>
|
||||
<div class="font-medium text-slate-800 dark:text-slate-100">{{item.customer}}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left">{{item.email}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left">{{item.location}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left">{{item.date}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="text-left text-emerald-500 font-medium">{{item.amount}}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div class="flex items-center">
|
||||
<button class="text-slate-400 hover:text-slate-500 dark:text-slate-500 dark:hover:text-slate-400 transform" :class="{ 'rotate-180': descriptionOpen }" @click.prevent="descriptionOpen = !descriptionOpen" :aria-expanded="descriptionOpen" :aria-controls="`description-${item.id}`">
|
||||
<span class="sr-only">Menu</span>
|
||||
<svg class="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!--
|
||||
Example of content revealing when clicking the button on the right side:
|
||||
Note that you must set a "colspan" attribute on the <td> element,
|
||||
and it should match the number of columns in your table
|
||||
-->
|
||||
<tr :id="`description-${item.id}`" role="region" :class="!descriptionOpen && 'hidden'">
|
||||
<td colspan="10" class="px-2 first:pl-5 last:pr-5 py-3">
|
||||
<div class="bg-slate-50 dark:bg-slate-900/30 dark:text-slate-400 p-3 -mt-3">
|
||||
<div class="text-sm mb-3">
|
||||
<div class="font-medium text-slate-800 dark:text-slate-100 mb-1">{{item.descriptionTitle}}</div>
|
||||
<div>{{item.descriptionBody}}</div>
|
||||
</div>
|
||||
<button class="btn-xs bg-indigo-500 hover:bg-indigo-600 text-white">Approve</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'AccordionTableRichItem',
|
||||
props: ['item'],
|
||||
setup() {
|
||||
|
||||
const descriptionOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
return {
|
||||
descriptionOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
57
src/components/Banner.vue
Normal file
57
src/components/Banner.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="px-4 py-2 rounded-sm text-sm text-white" :class="typeColor(type)">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div class="font-medium">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Banner',
|
||||
props: ['type', 'open'],
|
||||
setup() {
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-500';
|
||||
case 'error':
|
||||
return 'bg-rose-500';
|
||||
case 'success':
|
||||
return 'bg-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-500';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
typeColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
57
src/components/Banner2.vue
Normal file
57
src/components/Banner2.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="px-4 py-2 rounded-sm text-sm border" :class="typeColor(type)">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Banner2',
|
||||
props: ['type', 'open'],
|
||||
setup() {
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 dark:bg-amber-400/30 border-amber-200 dark:border-transparent text-amber-600 dark:text-amber-400';
|
||||
case 'error':
|
||||
return 'bg-rose-100 dark:bg-rose-400/30 border-rose-200 dark:border-transparent text-rose-600 dark:text-rose-400';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 dark:bg-emerald-400/30 border-emerald-200 dark:border-transparent text-emerald-600 dark:text-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-100 dark:bg-indigo-400/30 border-indigo-200 dark:border-transparent text-indigo-500 dark:text-indigo-400';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
typeColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
121
src/components/DateSelect.vue
Normal file
121
src/components/DateSelect.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="btn justify-between min-w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-500 hover:text-slate-600 dark:text-slate-300 dark:hover:text-slate-200"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 fill-current text-slate-500 dark:text-slate-400 shrink-0 mr-2" viewBox="0 0 16 16">
|
||||
<path d="M15 2h-2V0h-2v2H9V0H7v2H5V0H3v2H1a1 1 0 00-1 1v12a1 1 0 001 1h14a1 1 0 001-1V3a1 1 0 00-1-1zm-1 12H2V6h12v8z" />
|
||||
</svg>
|
||||
<span>{{options[selected].period}}</span>
|
||||
</span>
|
||||
<svg class="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-100 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="z-10 absolute top-full right-0 w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1">
|
||||
<div
|
||||
ref="dropdown"
|
||||
class="font-medium text-sm text-slate-600 dark:text-slate-300"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
|
||||
<button
|
||||
v-for="option in options"
|
||||
:key="option.id"
|
||||
class="flex items-center w-full hover:bg-slate-50 hover:dark:bg-slate-700/20 py-1 px-3 cursor-pointer"
|
||||
:class="option.id === selected && 'text-indigo-500'"
|
||||
@click="selected = option.id; dropdownOpen = false"
|
||||
>
|
||||
<svg class="shrink-0 mr-2 fill-current text-indigo-500" :class="option.id !== selected && 'invisible'" width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
<span>{{option.period}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DateSelect',
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
const selected = ref(2)
|
||||
|
||||
const options = ref([
|
||||
{
|
||||
id: 0,
|
||||
period: 'Today'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Last 7 Days'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Last Month'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Last 12 Months'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: 'All Time'
|
||||
}
|
||||
])
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
selected,
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
44
src/components/Datepicker.vue
Normal file
44
src/components/Datepicker.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<flat-pickr class="form-input pl-9 dark:bg-slate-800 text-slate-500 hover:text-slate-600 dark:text-slate-300 dark:hover:text-slate-200 font-medium w-[15.5rem]" :config="config" v-model="date"></flat-pickr>
|
||||
<div class="absolute inset-0 right-auto flex items-center pointer-events-none">
|
||||
<svg class="w-4 h-4 fill-current text-slate-500 dark:text-slate-400 ml-3" viewBox="0 0 16 16">
|
||||
<path d="M15 2h-2V0h-2v2H9V0H7v2H5V0H3v2H1a1 1 0 00-1 1v12a1 1 0 001 1h14a1 1 0 001-1V3a1 1 0 00-1-1zm-1 12H2V6h12v8z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
|
||||
export default {
|
||||
name: 'Datepicker',
|
||||
props: ['align'],
|
||||
data (props) {
|
||||
return {
|
||||
date: null, // refer to https://github.com/ankurk91/vue-flatpickr-component
|
||||
config: {
|
||||
mode: 'range',
|
||||
static: true,
|
||||
monthSelectorType: 'static',
|
||||
dateFormat: 'M j, Y',
|
||||
defaultDate: [new Date().setDate(new Date().getDate() - 6), new Date()],
|
||||
prevArrow: '<svg class="fill-current" width="7" height="11" viewBox="0 0 7 11"><path d="M5.4 10.8l1.4-1.4-4-4 4-4L5.4 0 0 5.4z" /></svg>',
|
||||
nextArrow: '<svg class="fill-current" width="7" height="11" viewBox="0 0 7 11"><path d="M1.4 10.8L0 9.4l4-4-4-4L1.4 0l5.4 5.4z" /></svg>',
|
||||
onReady: (selectedDates, dateStr, instance) => {
|
||||
instance.element.value = dateStr.replace('to', '-');
|
||||
const customClass = (props.align) ? props.align : '';
|
||||
instance.calendarContainer.classList.add(`flatpickr-${customClass}`);
|
||||
},
|
||||
onChange: (selectedDates, dateStr, instance) => {
|
||||
instance.element.value = dateStr.replace('to', '-');
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
flatPickr
|
||||
},
|
||||
}
|
||||
</script>
|
||||
118
src/components/DropdownClassic.vue
Normal file
118
src/components/DropdownClassic.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="btn justify-between min-w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-500 hover:text-slate-600 dark:text-slate-300 dark:hover:text-slate-200"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<span>{{options[selected].period}}</span>
|
||||
</span>
|
||||
<svg class="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-100 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="z-10 absolute top-full left-0 w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1">
|
||||
<div
|
||||
ref="dropdown"
|
||||
class="font-medium text-sm text-slate-600 dark:text-slate-300"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
|
||||
<button
|
||||
v-for="option in options"
|
||||
:key="option.id"
|
||||
class="flex items-center w-full hover:bg-slate-50 hover:dark:bg-slate-700/20 py-1 px-3 cursor-pointer"
|
||||
:class="option.id === selected && 'text-indigo-500'"
|
||||
@click="selected = option.id; dropdownOpen = false"
|
||||
>
|
||||
<svg class="shrink-0 mr-2 fill-current text-indigo-500" :class="option.id !== selected && 'invisible'" width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
<span>{{option.period}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownClassic',
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
const selected = ref(2)
|
||||
|
||||
const options = ref([
|
||||
{
|
||||
id: 0,
|
||||
period: 'Today'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Last 7 Days'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Last Month'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Last 12 Months'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: 'All Time'
|
||||
}
|
||||
])
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
selected,
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
src/components/DropdownEditMenu.vue
Normal file
80
src/components/DropdownEditMenu.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
ref="trigger"
|
||||
class="rounded-full"
|
||||
:class="dropdownOpen ? 'bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400' : 'text-slate-400 hover:text-slate-500 dark:text-slate-500 dark:hover:text-slate-400'"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="sr-only">Menu</span>
|
||||
<svg class="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="2" />
|
||||
<circle cx="10" cy="16" r="2" />
|
||||
<circle cx="22" cy="16" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full min-w-36 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownEditMenu',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
src/components/DropdownEditMenuCard.vue
Normal file
80
src/components/DropdownEditMenuCard.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
ref="trigger"
|
||||
class="rounded-full"
|
||||
:class="dropdownOpen ? 'bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-300' : 'bg-white dark:bg-slate-700 text-slate-400 hover:text-slate-500 dark:text-slate-400 dark:hover:text-slate-300'"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="sr-only">Menu</span>
|
||||
<svg class="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="2" />
|
||||
<circle cx="10" cy="16" r="2" />
|
||||
<circle cx="22" cy="16" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full min-w-36 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownEditMenuCard',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
121
src/components/DropdownFilter.vue
Normal file
121
src/components/DropdownFilter.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="btn bg-white dark:bg-slate-800 border-slate-200 hover:border-slate-300 dark:border-slate-700 dark:hover:border-slate-600 text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="sr-only">Filter</span><wbr />
|
||||
<svg class="w-4 h-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M9 15H7a1 1 0 010-2h2a1 1 0 010 2zM11 11H5a1 1 0 010-2h6a1 1 0 010 2zM13 7H3a1 1 0 010-2h10a1 1 0 010 2zM15 3H1a1 1 0 010-2h14a1 1 0 010 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full left-0 right-auto min-w-56 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 pt-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'md:left-auto md:right-0' : 'md:left-0 md:right-auto'">
|
||||
<div ref="dropdown">
|
||||
<div class="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase pt-1.5 pb-2 px-3">Filters</div>
|
||||
<ul class="mb-4">
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Direct VS Indirect</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Real Time Value</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Top Channels</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Sales VS Refunds</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Last Order</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="py-1 px-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm font-medium ml-2">Total Spent</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="py-2 px-3 border-t border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-700/20">
|
||||
<ul class="flex items-center justify-between">
|
||||
<li>
|
||||
<button class="btn-xs bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-500 dark:text-slate-300 hover:text-slate-600 dark:hover:text-slate-200">Clear</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn-xs bg-indigo-500 hover:bg-indigo-600 text-white" @click="dropdownOpen = false" @focusout="dropdownOpen = false">Apply</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownFilter',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
114
src/components/DropdownFull.vue
Normal file
114
src/components/DropdownFull.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="relative inline-flex w-full">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="btn w-full justify-between min-w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-500 hover:text-slate-600 dark:text-slate-300 dark:hover:text-slate-200"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<span>{{options[selected].period}}</span>
|
||||
</span>
|
||||
<svg class="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-100 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="z-10 absolute top-full left-0 w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1">
|
||||
<div
|
||||
ref="dropdown"
|
||||
class="font-medium text-sm text-slate-600 dark:text-slate-300 divide-y divide-slate-200 dark:divide-slate-700"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
|
||||
<button
|
||||
v-for="option in options"
|
||||
:key="option.id"
|
||||
class="flex items-center justify-between w-full hover:bg-slate-50 dark:hover:bg-slate-700/20 py-2 px-3 cursor-pointer"
|
||||
:class="option.id === selected && 'text-indigo-500'"
|
||||
@click="selected = option.id; dropdownOpen = false"
|
||||
>
|
||||
<span>{{option.period}}</span>
|
||||
<svg class="shrink-0 ml-2 fill-current text-indigo-400" :class="option.id !== selected && 'invisible'" width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownFull',
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
const selected = ref(0)
|
||||
|
||||
const options = ref([
|
||||
{
|
||||
id: 0,
|
||||
period: 'Most Popular'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Newest'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Lowest Price'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Highest Price'
|
||||
}
|
||||
])
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
selected,
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
103
src/components/DropdownHelp.vue
Normal file
103
src/components/DropdownHelp.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="w-8 h-8 flex items-center justify-center bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600/80 rounded-full"
|
||||
:class="{ 'bg-slate-200': dropdownOpen }"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="sr-only">Info</span>
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-current text-slate-500 dark:text-slate-400" d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full min-w-44 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<div class="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase pt-1.5 pb-2 px-3">Need help?</div>
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li>
|
||||
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
|
||||
<svg class="w-3 h-3 fill-current text-indigo-300 dark:text-indigo-500 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<rect y="3" width="12" height="9" rx="1" />
|
||||
<path d="M2 0h8v2H2z" />
|
||||
</svg>
|
||||
<span>Documentation</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
|
||||
<svg class="w-3 h-3 fill-current text-indigo-300 dark:text-indigo-500 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<path d="M10.5 0h-9A1.5 1.5 0 000 1.5v9A1.5 1.5 0 001.5 12h9a1.5 1.5 0 001.5-1.5v-9A1.5 1.5 0 0010.5 0zM10 7L8.207 5.207l-3 3-1.414-1.414 3-3L5 2h5v5z" />
|
||||
</svg>
|
||||
<span>Support Site</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
|
||||
<svg class="w-3 h-3 fill-current text-indigo-300 dark:text-indigo-500 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<path d="M11.854.146a.5.5 0 00-.525-.116l-11 4a.5.5 0 00-.015.934l4.8 1.921 1.921 4.8A.5.5 0 007.5 12h.008a.5.5 0 00.462-.329l4-11a.5.5 0 00-.116-.525z" />
|
||||
</svg>
|
||||
<span>Contact us</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownHelp',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
98
src/components/DropdownNotifications.vue
Normal file
98
src/components/DropdownNotifications.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="w-8 h-8 flex items-center justify-center bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600/80 rounded-full"
|
||||
:class="{ 'bg-slate-200': dropdownOpen }"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<span class="sr-only">Notifications</span>
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-current text-slate-500 dark:text-slate-400" d="M6.5 0C2.91 0 0 2.462 0 5.5c0 1.075.37 2.074 1 2.922V12l2.699-1.542A7.454 7.454 0 006.5 11c3.59 0 6.5-2.462 6.5-5.5S10.09 0 6.5 0z" />
|
||||
<path class="fill-current text-slate-400 dark:text-slate-500" d="M16 9.5c0-.987-.429-1.897-1.147-2.639C14.124 10.348 10.66 13 6.5 13c-.103 0-.202-.018-.305-.021C7.231 13.617 8.556 14 10 14c.449 0 .886-.04 1.307-.11L15 16v-4h-.012C15.627 11.285 16 10.425 16 9.5z" />
|
||||
</svg>
|
||||
<div class="absolute top-0 right-0 w-2.5 h-2.5 bg-rose-500 border-2 border-white dark:border-[#182235] rounded-full"></div>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full -mr-48 sm:mr-0 min-w-80 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<div class="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase pt-1.5 pb-2 px-4">Notifications</div>
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li class="border-b border-slate-200 dark:border-slate-700 last:border-0">
|
||||
<router-link class="block py-2 px-4 hover:bg-slate-50 dark:hover:bg-slate-700/20" to="#0" @click="dropdownOpen = false">
|
||||
<span class="block text-sm mb-2">📣 <span class="font-medium text-slate-800 dark:text-slate-100">Edit your information in a swipe</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span class="block text-xs font-medium text-slate-400 dark:text-slate-500">Feb 12, 2021</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="border-b border-slate-200 dark:border-slate-700 last:border-0">
|
||||
<router-link class="block py-2 px-4 hover:bg-slate-50 dark:hover:bg-slate-700/20" to="#0" @click="dropdownOpen = false">
|
||||
<span class="block text-sm mb-2">📣 <span class="font-medium text-slate-800 dark:text-slate-100">Edit your information in a swipe</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span class="block text-xs font-medium text-slate-400 dark:text-slate-500">Feb 9, 2021</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="border-b border-slate-200 dark:border-slate-700 last:border-0">
|
||||
<router-link class="block py-2 px-4 hover:bg-slate-50 dark:hover:bg-slate-700/20" to="#0" @click="dropdownOpen = false">
|
||||
<span class="block text-sm mb-2">🚀<span class="font-medium text-slate-800 dark:text-slate-100">Say goodbye to paper receipts!</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span class="block text-xs font-medium text-slate-400 dark:text-slate-500">Jan 24, 2020</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownNotifications',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
95
src/components/DropdownProfile.vue
Normal file
95
src/components/DropdownProfile.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<img class="w-8 h-8 rounded-full" :src="UserAvatar" width="32" height="32" alt="User" />
|
||||
<div class="flex items-center truncate">
|
||||
<span class="truncate ml-2 text-sm font-medium dark:text-slate-300 group-hover:text-slate-800 dark:group-hover:text-slate-200">Acme Inc.</span>
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full min-w-44 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<div class="pt-0.5 pb-2 px-3 mb-1 border-b border-slate-200 dark:border-slate-700">
|
||||
<div class="font-medium text-slate-800 dark:text-slate-100">Acme Inc.</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400 italic">Administrator</div>
|
||||
</div>
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li>
|
||||
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 flex items-center py-1 px-3" to="/settings/account" @click="dropdownOpen = false">Settings</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400 flex items-center py-1 px-3" to="/signin" @click="dropdownOpen = false">Sign Out</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import UserAvatar from '../images/user-avatar-32.png'
|
||||
|
||||
export default {
|
||||
name: 'DropdownProfile',
|
||||
props: ['align'],
|
||||
data() {
|
||||
return {
|
||||
UserAvatar: UserAvatar,
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
84
src/components/DropdownSort.vue
Normal file
84
src/components/DropdownSort.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<div class="flex items-center truncate">
|
||||
<span class="truncate font-medium text-indigo-500 group-hover:text-indigo-600 dark:group-hover:text-indigo-400">Newest</span>
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-indigo-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 flex items-center py-1 px-3" href="#0" @click="dropdownOpen = false">Oldest</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 flex items-center py-1 px-3" href="#0" @click="dropdownOpen = false">Popular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownSort',
|
||||
props: ['align'],
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
111
src/components/DropdownSwitch.vue
Normal file
111
src/components/DropdownSwitch.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="grow flex items-center truncate"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<img class="w-8 h-8 rounded-full mr-2" src="../images/user-avatar-32.png" width="32" height="32" alt="Group 01" />
|
||||
<div class="truncate">
|
||||
<span class="text-sm font-medium dark:text-slate-300 group-hover:text-slate-800 dark:group-hover:text-slate-200">Acme Inc.</span>
|
||||
</div>
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full left-0 min-w-60 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1">
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 block py-1.5 px-3" href="#0" @click="dropdownOpen = false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center truncate">
|
||||
<img class="w-7 h-7 rounded-full mr-2" src="../images/channel-01.png" width="28" height="28" alt="Channel 01" />
|
||||
<div class="truncate">Acme Inc.</div>
|
||||
</div>
|
||||
<svg class="w-3 h-3 shrink-0 fill-current text-indigo-500 ml-1" viewBox="0 0 12 12">
|
||||
<path d="M10.28 1.28L3.989 7.575 1.695 5.28A1 1 0 00.28 6.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 1.28z" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 block py-1.5 px-3" href="#0" @click="dropdownOpen = false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center truncate">
|
||||
<img class="w-7 h-7 rounded-full mr-2" src="../images/channel-02.png" width="28" height="28" alt="Channel 02" />
|
||||
<div class="truncate">Acme Limited</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 block py-1.5 px-3" href="#0" @click="dropdownOpen = false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center truncate">
|
||||
<img class="w-7 h-7 rounded-full mr-2" src="../images/channel-03.png" width="28" height="28" alt="Channel 03" />
|
||||
<div class="truncate">Acme Srl</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'DropdownSwitch',
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
90
src/components/DropdownTransaction.vue
Normal file
90
src/components/DropdownTransaction.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="relative inline-flex">
|
||||
<button
|
||||
ref="trigger"
|
||||
class="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
:aria-expanded="dropdownOpen"
|
||||
>
|
||||
<div class="flex items-center truncate">
|
||||
<span class="truncate font-medium text-indigo-500 group-hover:text-indigo-600 dark:group-hover:text-indigo-400">My Personal Account</span>
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-indigo-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="dropdownOpen" class="origin-top-right z-10 absolute top-full min-w-44 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
|
||||
<ul
|
||||
ref="dropdown"
|
||||
@focusin="dropdownOpen = true"
|
||||
@focusout="dropdownOpen = false"
|
||||
>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 flex items-center py-1 px-3" href="#0" @click="dropdownOpen = false">Business Account</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-slate-600 dark:text-slate-300 hover:text-slate-800 dark:hover:text-slate-200 flex items-center py-1 px-3" href="#0" @click="dropdownOpen = false">Family Account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import UserAvatar from '../images/user-avatar-32.png'
|
||||
|
||||
export default {
|
||||
name: 'DropdownProfile',
|
||||
props: ['align'],
|
||||
data() {
|
||||
return {
|
||||
UserAvatar: UserAvatar,
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const trigger = ref(null)
|
||||
const dropdown = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdownOpen.value || dropdown.value.contains(target) || trigger.value.contains(target)) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen.value || keyCode !== 27) return
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
dropdownOpen,
|
||||
trigger,
|
||||
dropdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
79
src/components/ModalAction.vue
Normal file
79
src/components/ModalAction.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<!-- Modal backdrop -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="modalOpen" class="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity" aria-hidden="true"></div>
|
||||
</transition>
|
||||
<!-- Modal dialog -->
|
||||
<transition
|
||||
enter-active-class="transition ease-in-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in-out duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-show="modalOpen" :id="id" class="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center px-4 sm:px-6" role="dialog" aria-modal="true">
|
||||
<div ref="modalContent" class="bg-white dark:bg-slate-800 rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<div class="p-6">
|
||||
<div class="relative">
|
||||
<!-- Close button -->
|
||||
<button class="absolute top-0 right-0 text-slate-400 dark:text-slate-500 hover:text-slate-500 dark:hover:text-slate-400" @click.stop="$emit('close-modal')">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalAction',
|
||||
props: ['id', 'modalOpen'],
|
||||
emits: ['close-modal'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const modalContent = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!props.modalOpen || modalContent.value.contains(target)) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.modalOpen || keyCode !== 27) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
modalContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
src/components/ModalBasic.vue
Normal file
80
src/components/ModalBasic.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<!-- Modal backdrop -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="modalOpen" class="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity" aria-hidden="true"></div>
|
||||
</transition>
|
||||
<!-- Modal dialog -->
|
||||
<transition
|
||||
enter-active-class="transition ease-in-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in-out duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-show="modalOpen" :id="id" class="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center px-4 sm:px-6" role="dialog" aria-modal="true">
|
||||
<div ref="modalContent" class="bg-white dark:bg-slate-800 rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<!-- Modal header -->
|
||||
<div class="px-5 py-3 border-b border-slate-200 dark:border-slate-700">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-semibold text-slate-800 dark:text-slate-100">{{ title }}</div>
|
||||
<button class="text-slate-400 dark:text-slate-500 hover:text-slate-500 dark:hover:text-slate-400" @click.stop="$emit('close-modal')">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalBasic',
|
||||
props: ['id', 'modalOpen', 'title'],
|
||||
emits: ['close-modal'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const modalContent = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!props.modalOpen || modalContent.value.contains(target)) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.modalOpen || keyCode !== 27) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
modalContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
68
src/components/ModalBlank.vue
Normal file
68
src/components/ModalBlank.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<!-- Modal backdrop -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="modalOpen" class="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity" aria-hidden="true"></div>
|
||||
</transition>
|
||||
<!-- Modal dialog -->
|
||||
<transition
|
||||
enter-active-class="transition ease-in-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in-out duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-show="modalOpen" :id="id" class="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center px-4 sm:px-6" role="dialog" aria-modal="true">
|
||||
<div ref="modalContent" class="bg-white dark:bg-slate-800 rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalEmpty',
|
||||
props: ['id', 'modalOpen'],
|
||||
emits: ['close-modal'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const modalContent = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!props.modalOpen || modalContent.value.contains(target)) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.modalOpen || keyCode !== 27) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
modalContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
82
src/components/ModalCookies.vue
Normal file
82
src/components/ModalCookies.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<!-- Modal backdrop -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="modalOpen" class="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity" aria-hidden="true"></div>
|
||||
</transition>
|
||||
<!-- Modal dialog -->
|
||||
<transition
|
||||
enter-active-class="transition ease-in-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in-out duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-show="modalOpen" :id="id" class="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center px-4 sm:px-6" role="dialog" aria-modal="true">
|
||||
<div ref="modalContent" class="bg-white dark:bg-slate-800 rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<div class="p-5">
|
||||
<!-- Modal header -->
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-lg font-semibold text-slate-800 dark:text-slate-100">{{ title }}</div>
|
||||
<button class="text-slate-400 dark:text-slate-500 hover:text-slate-500 dark:hover:text-slate-400" @click.stop="$emit('close-modal')">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalCookies',
|
||||
props: ['id', 'modalOpen', 'title'],
|
||||
emits: ['close-modal'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const modalContent = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!props.modalOpen || modalContent.value.contains(target)) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.modalOpen || keyCode !== 27) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
return {
|
||||
modalContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
164
src/components/ModalSearch.vue
Normal file
164
src/components/ModalSearch.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<!-- Modal backdrop -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition ease-out duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="modalOpen" class="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity" aria-hidden="true"></div>
|
||||
</transition>
|
||||
<!-- Modal dialog -->
|
||||
<transition
|
||||
enter-active-class="transition ease-in-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in-out duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div v-show="modalOpen" :id="id" class="fixed inset-0 z-50 overflow-hidden flex items-start top-20 mb-4 justify-center px-4 sm:px-6" role="dialog" aria-modal="true">
|
||||
<div ref="modalContent" class="bg-white dark:bg-slate-800 border border-transparent dark:border-slate-700 overflow-auto max-w-2xl w-full max-h-full rounded shadow-lg">
|
||||
<!-- Search form -->
|
||||
<form class="border-b border-slate-200 dark:border-slate-700">
|
||||
<div class="relative">
|
||||
<label :for="searchId" class="sr-only">Search</label>
|
||||
<input :id="searchId" class="w-full dark:text-slate-300 bg-white dark:bg-slate-800 border-0 focus:ring-transparent placeholder-slate-400 dark:placeholder-slate-500 appearance-none py-3 pl-10 pr-4" type="search" placeholder="Search Anything…" ref="searchInput" />
|
||||
<button class="absolute inset-0 right-auto group" type="submit" aria-label="Search">
|
||||
<svg class="w-4 h-4 shrink-0 fill-current text-slate-400 dark:text-slate-500 group-hover:text-slate-500 dark:group-hover:text-slate-400 ml-4 mr-2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" />
|
||||
<path d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="py-4 px-2">
|
||||
<!-- Recent searches -->
|
||||
<div class="mb-3 last:mb-0">
|
||||
<div class="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase px-2 mb-2">Recent searches</div>
|
||||
<ul class="text-sm">
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Form Builder - 23 hours on-demand video</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Access Mosaic on mobile and TV</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Product Update - Q4 2021</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Master Digital Marketing Strategy course</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Dedicated forms for products</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Product Update - Q4 2021</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Recent pages -->
|
||||
<div class="mb-3 last:mb-0">
|
||||
<div class="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase px-2 mb-2">Recent pages</div>
|
||||
<ul class="text-sm">
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1zM3 2h10v8H9v4H3V2z" />
|
||||
</svg>
|
||||
<span><span class="font-medium">Messages</span> - <span class="text-slate-600 dark:text-slate-400 group-hover:text-white">Conversation / … / Mike Mills</span></span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link class="flex items-center p-2 text-slate-800 dark:text-slate-100 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1zM3 2h10v8H9v4H3V2z" />
|
||||
</svg>
|
||||
<span><span class="font-medium">Messages</span> - <span class="text-slate-600 dark:text-slate-400 group-hover:text-white">Conversation / … / Eva Patrick</span></span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalSearch',
|
||||
props: ['id', 'searchId', 'modalOpen'],
|
||||
emits: ['open-modal', 'close-modal'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const modalContent = ref(null)
|
||||
const searchInput = ref(null)
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!props.modalOpen || modalContent.value.contains(target)) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.modalOpen || keyCode !== 27) return
|
||||
emit('close-modal')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
watch(() => props.modalOpen, (open) => {
|
||||
open && nextTick(() => searchInput.value.focus())
|
||||
})
|
||||
|
||||
return {
|
||||
modalContent,
|
||||
searchInput,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
41
src/components/Notification.vue
Normal file
41
src/components/Notification.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="inline-flex flex-col w-full max-w-lg px-4 py-2 rounded-sm text-sm bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current text-amber-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current text-rose-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current text-emerald-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current text-indigo-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-right mt-1">
|
||||
<a class="font-medium text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400" href="#0">Action -></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Notification',
|
||||
props: ['type', 'open'],
|
||||
}
|
||||
</script>
|
||||
23
src/components/PaginationClassic.vue
Normal file
23
src/components/PaginationClassic.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<nav class="mb-4 sm:mb-0 sm:order-1" role="navigation" aria-label="Navigation">
|
||||
<ul class="flex justify-center">
|
||||
<li class="ml-3 first:ml-0">
|
||||
<span class="btn bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-300 dark:text-slate-600"><- Previous</span>
|
||||
</li>
|
||||
<li class="ml-3 first:ml-0">
|
||||
<a class="btn bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-indigo-500" href="#0">Next -></a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400 text-center sm:text-left">
|
||||
Showing <span class="font-medium text-slate-600 dark:text-slate-300">1</span> to <span class="font-medium text-slate-600 dark:text-slate-300">10</span> of <span class="font-medium text-slate-600 dark:text-slate-300">467</span> results
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PaginationClassic',
|
||||
}
|
||||
</script>
|
||||
45
src/components/PaginationNumeric.vue
Normal file
45
src/components/PaginationNumeric.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<nav class="flex" role="navigation" aria-label="Navigation">
|
||||
<div class="mr-2">
|
||||
<span class="inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-300 dark:text-slate-600">
|
||||
<span class="sr-only">Previous</span><wbr />
|
||||
<svg class="h-4 w-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M9.4 13.4l1.4-1.4-4-4 4-4-1.4-1.4L4 8z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="inline-flex text-sm font-medium -space-x-px shadow-sm">
|
||||
<li>
|
||||
<span class="inline-flex items-center justify-center rounded-l leading-5 px-3.5 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-indigo-500">1</span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white dark:bg-slate-800 hover:bg-indigo-500 dark:hover:bg-indigo-500 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:text-white" href="#0">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white dark:bg-slate-800 hover:bg-indigo-500 dark:hover:bg-indigo-500 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:text-white" href="#0">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-400 dark:text-slate-500">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center rounded-r leading-5 px-3.5 py-2 bg-white dark:bg-slate-800 hover:bg-indigo-500 dark:hover:bg-indigo-500 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:text-white" href="#0">9</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="ml-2">
|
||||
<a href="#0" class="inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white dark:bg-slate-800 hover:bg-indigo-500 dark:hover:bg-indigo-500 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:text-white shadow-sm">
|
||||
<span class="sr-only">Next</span><wbr />
|
||||
<svg class="h-4 w-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M6.6 13.4L5.2 12l4-4-4-4 1.4-1.4L12 8z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PaginationNumeric',
|
||||
}
|
||||
</script>
|
||||
37
src/components/PaginationNumeric2.vue
Normal file
37
src/components/PaginationNumeric2.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="flex justify-between" role="navigation" aria-label="Navigation">
|
||||
<div class="flex-1 mr-2">
|
||||
<span class="btn bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-300 dark:text-slate-600"><-<span class="hidden sm:inline"> Previous</span></span>
|
||||
</div>
|
||||
<div class="grow text-center">
|
||||
<ul class="inline-flex text-sm font-medium -space-x-px">
|
||||
<li>
|
||||
<span class="inline-flex items-center justify-center rounded-full leading-5 px-2 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-indigo-500 shadow-sm"><span class="w-5">1</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-600 dark:text-slate-300 hover:text-indigo-500 dark:hover:text-indigo-500 border border-transparent" href="#0"><span class="w-5">2</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-600 dark:text-slate-300 hover:text-indigo-500 dark:hover:text-indigo-500 border border-transparent" href="#0"><span class="w-5">3</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-400 dark:text-slate-500">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="inline-flex items-center justify-center rounded-r leading-5 px-2 py-2 text-slate-600 dark:text-slate-300 hover:text-indigo-500 dark:hover:text-indigo-500 border border-transparent" href="#0"><span class="w-5">9</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex-1 text-right ml-2">
|
||||
<a class="btn bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-indigo-500" href="#0"><span class="hidden sm:inline">Next </span>-></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PaginationNumeric2',
|
||||
}
|
||||
</script>
|
||||
23
src/components/SearchForm.vue
Normal file
23
src/components/SearchForm.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<form class="relative">
|
||||
<label for="action-search" class="sr-only">Search</label>
|
||||
<input id="action-search" class="form-input pl-9 bg-white dark:bg-slate-800" type="search" :placeholder="placeholder" />
|
||||
<button class="absolute inset-0 right-auto group" type="submit" aria-label="Search">
|
||||
<svg class="w-4 h-4 shrink-0 fill-current text-slate-400 dark:text-slate-500 group-hover:text-slate-500 dark:group-hover:text-slate-400 ml-3 mr-2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" />
|
||||
<path d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
props: {
|
||||
placeholder: {
|
||||
default: 'Search…'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
23
src/components/ThemeToggle.vue
Normal file
23
src/components/ThemeToggle.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<input type="checkbox" name="light-switch" id="light-switch" v-model="isDark" class="light-switch sr-only" />
|
||||
<label class="flex items-center justify-center cursor-pointer w-8 h-8 bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600/80 rounded-full" for="light-switch">
|
||||
<svg class="w-4 h-4 dark:hidden" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-current text-slate-400" d="M7 0h2v2H7V0Zm5.88 1.637 1.414 1.415-1.415 1.413-1.414-1.414 1.415-1.414ZM14 7h2v2h-2V7Zm-1.05 7.433-1.415-1.414 1.414-1.414 1.415 1.413-1.414 1.415ZM7 14h2v2H7v-2Zm-4.02.363L1.566 12.95l1.415-1.414 1.414 1.415-1.415 1.413ZM0 7h2v2H0V7Zm3.05-5.293L4.465 3.12 3.05 4.535 1.636 3.121 3.05 1.707Z" />
|
||||
<path class="fill-current text-slate-500" d="M8 4C5.8 4 4 5.8 4 8s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4Z" />
|
||||
</svg>
|
||||
<svg class="w-4 h-4 hidden dark:block" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-current text-slate-400" d="M6.2 2C3.2 2.8 1 5.6 1 8.9 1 12.8 4.2 16 8.1 16c3.3 0 6-2.2 6.9-5.2C9.7 12.2 4.8 7.3 6.2 2Z" />
|
||||
<path class="fill-current text-slate-500" d="M12.5 6a.625.625 0 0 1-.625-.625 1.252 1.252 0 0 0-1.25-1.25.625.625 0 1 1 0-1.25 1.252 1.252 0 0 0 1.25-1.25.625.625 0 1 1 1.25 0c.001.69.56 1.249 1.25 1.25a.625.625 0 1 1 0 1.25c-.69.001-1.249.56-1.25 1.25A.625.625 0 0 1 12.5 6Z" />
|
||||
</svg>
|
||||
<span class="sr-only">Switch to light / dark version</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDark } from "@vueuse/core";
|
||||
const isDark = useDark({
|
||||
selector: 'html',
|
||||
})
|
||||
</script>
|
||||
57
src/components/Toast.vue
Normal file
57
src/components/Toast.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="inline-flex min-w-80 px-4 py-2 rounded-sm text-sm text-white" :class="typeColor(type)">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div class="font-medium">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Toast',
|
||||
props: ['type', 'open'],
|
||||
setup() {
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-500';
|
||||
case 'error':
|
||||
return 'bg-rose-500';
|
||||
case 'success':
|
||||
return 'bg-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-500';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
typeColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
57
src/components/Toast2.vue
Normal file
57
src/components/Toast2.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="inline-flex min-w-80 px-4 py-2 rounded-sm text-sm border" :class="typeColor(type)">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Toast2',
|
||||
props: ['type', 'open'],
|
||||
setup() {
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 dark:bg-amber-400/30 border-amber-200 dark:border-transparent text-amber-600 dark:text-amber-400';
|
||||
case 'error':
|
||||
return 'bg-rose-100 dark:bg-rose-400/30 border-rose-200 dark:border-transparent text-rose-600 dark:text-rose-400';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 dark:bg-emerald-400/30 border-emerald-200 dark:border-transparent text-emerald-600 dark:text-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-100 dark:bg-indigo-400/30 border-indigo-200 dark:border-transparent text-indigo-500 dark:text-indigo-400';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
typeColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
38
src/components/Toast3.vue
Normal file
38
src/components/Toast3.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div v-show="open" role="alert">
|
||||
<div class="inline-flex min-w-80 px-4 py-2 rounded-sm text-sm bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-100">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg v-if="type === 'warning'" class="w-4 h-4 shrink-0 fill-current text-amber-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'error'" class="w-4 h-4 shrink-0 fill-current text-rose-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
<svg v-else-if="type === 'success'" class="w-4 h-4 shrink-0 fill-current text-emerald-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 shrink-0 fill-current text-indigo-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<button class="dark:text-slate-400 opacity-70 hover:opacity-80 ml-3 mt-[3px]" @click="open = false">
|
||||
<div class="sr-only">Close</div>
|
||||
<svg class="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Toast3',
|
||||
props: ['type', 'open'],
|
||||
}
|
||||
</script>
|
||||
112
src/components/Tooltip.vue
Normal file
112
src/components/Tooltip.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative"
|
||||
@mouseenter="tooltipOpen = true"
|
||||
@mouseleave="tooltipOpen = false"
|
||||
@focusin="tooltipOpen = true"
|
||||
@focusout="tooltipOpen = false"
|
||||
>
|
||||
<button
|
||||
class="block"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="tooltipOpen"
|
||||
@click.prevent
|
||||
>
|
||||
<svg class="w-4 h-4 fill-current text-slate-400 dark:text-slate-500" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="z-10 absolute" :class="positionOuterClasses(position)">
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200 transform"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-out duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-show="tooltipOpen" class="rounded border overflow-hidden shadow-lg"
|
||||
:class="[
|
||||
colorClasses(bg),
|
||||
sizeClasses(size),
|
||||
positionInnerClasses(position)
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'Tooltip',
|
||||
props: ['bg', 'size', 'position'],
|
||||
setup() {
|
||||
|
||||
const tooltipOpen = ref(false)
|
||||
|
||||
const positionOuterClasses = (position) => {
|
||||
switch (position) {
|
||||
case 'right':
|
||||
return 'left-full top-1/2 -translate-y-1/2';
|
||||
case 'left':
|
||||
return 'right-full top-1/2 -translate-y-1/2';
|
||||
case 'bottom':
|
||||
return 'top-full left-1/2 -translate-x-1/2';
|
||||
default:
|
||||
return 'bottom-full left-1/2 -translate-x-1/2';
|
||||
}
|
||||
}
|
||||
|
||||
const sizeClasses = (size) => {
|
||||
switch (size) {
|
||||
case 'lg':
|
||||
return 'min-w-72 p-3';
|
||||
case 'md':
|
||||
return 'min-w-56 p-3';
|
||||
case 'sm':
|
||||
return 'min-w-44 p-2';
|
||||
default:
|
||||
return 'p-2';
|
||||
}
|
||||
}
|
||||
|
||||
const colorClasses = (bg) => {
|
||||
switch (bg) {
|
||||
case 'light':
|
||||
return 'bg-white text-slate-600 border-slate-200'
|
||||
case 'dark':
|
||||
return 'bg-slate-700 text-slate-100 border-slate-600'
|
||||
default:
|
||||
return 'text-slate-600 bg-white dark:bg-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-600'
|
||||
}
|
||||
}
|
||||
|
||||
const positionInnerClasses = (position) => {
|
||||
switch (position) {
|
||||
case 'right':
|
||||
return 'ml-2';
|
||||
case 'left':
|
||||
return 'mr-2';
|
||||
case 'bottom':
|
||||
return 'mt-2';
|
||||
default:
|
||||
return 'mb-2';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tooltipOpen,
|
||||
positionOuterClasses,
|
||||
sizeClasses,
|
||||
colorClasses,
|
||||
positionInnerClasses,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
239
src/css/additional-styles/flatpickr.css
Normal file
239
src/css/additional-styles/flatpickr.css
Normal file
@@ -0,0 +1,239 @@
|
||||
@import 'flatpickr/dist/flatpickr.min.css';
|
||||
|
||||
/* Customise flatpickr */
|
||||
* {
|
||||
--calendarPadding: 24px;
|
||||
--daySize: 36px;
|
||||
--daysWidth: calc(var(--daySize)*7);
|
||||
}
|
||||
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -8px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
border: inherit;
|
||||
@apply bg-white dark:bg-slate-800 rounded shadow-lg border border-slate-200 dark:border-slate-700 left-1/2;
|
||||
margin-left: calc(calc(var(--daysWidth) + calc(var(--calendarPadding)*2))*0.5*-1);
|
||||
padding: var(--calendarPadding);
|
||||
width: calc(var(--daysWidth) + calc(var(--calendarPadding)*2));
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.flatpickr-calendar {
|
||||
@apply left-0 right-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-right.flatpickr-calendar {
|
||||
@apply right-0 left-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.animate.open {
|
||||
animation: fpFadeInDown 200ms ease-out;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.flatpickr-days {
|
||||
width: var(--daysWidth);
|
||||
}
|
||||
|
||||
.dayContainer {
|
||||
width: var(--daysWidth);
|
||||
min-width: var(--daysWidth);
|
||||
max-width: var(--daysWidth);
|
||||
}
|
||||
|
||||
.flatpickr-day {
|
||||
@apply bg-slate-50 dark:bg-slate-700/20 text-sm font-medium text-slate-600 dark:text-slate-100;
|
||||
max-width: var(--daySize);
|
||||
height: var(--daySize);
|
||||
line-height: var(--daySize);
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.selected.startRange,
|
||||
.flatpickr-day.startRange.startRange,
|
||||
.flatpickr-day.endRange.startRange,
|
||||
.flatpickr-day.selected.endRange,
|
||||
.flatpickr-day.startRange.endRange,
|
||||
.flatpickr-day.endRange.endRange,
|
||||
.flatpickr-day.selected.startRange.endRange,
|
||||
.flatpickr-day.startRange.startRange.endRange,
|
||||
.flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply text-slate-400 dark:text-slate-500;
|
||||
}
|
||||
|
||||
.rangeMode .flatpickr-day {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.startRange,
|
||||
.flatpickr-day.endRange,
|
||||
.flatpickr-day.selected.inRange,
|
||||
.flatpickr-day.startRange.inRange,
|
||||
.flatpickr-day.endRange.inRange,
|
||||
.flatpickr-day.selected:focus,
|
||||
.flatpickr-day.startRange:focus,
|
||||
.flatpickr-day.endRange:focus,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.startRange:hover,
|
||||
.flatpickr-day.endRange:hover,
|
||||
.flatpickr-day.selected.prevMonthDay,
|
||||
.flatpickr-day.startRange.prevMonthDay,
|
||||
.flatpickr-day.endRange.prevMonthDay,
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
@apply bg-indigo-500 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.prevMonthDay.inRange,
|
||||
.flatpickr-day.nextMonthDay.inRange,
|
||||
.flatpickr-day.today.inRange,
|
||||
.flatpickr-day.prevMonthDay.today.inRange,
|
||||
.flatpickr-day.nextMonthDay.today.inRange,
|
||||
.flatpickr-day:hover,
|
||||
.flatpickr-day.prevMonthDay:hover,
|
||||
.flatpickr-day.nextMonthDay:hover,
|
||||
.flatpickr-day:focus,
|
||||
.flatpickr-day.prevMonthDay:focus,
|
||||
.flatpickr-day.nextMonthDay:focus,
|
||||
.flatpickr-day.today:hover,
|
||||
.flatpickr-day.today:focus {
|
||||
@apply bg-indigo-400 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.flatpickr-months {
|
||||
align-items: center;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
position: static;
|
||||
height: auto;
|
||||
@apply text-slate-600 hover:text-slate-900 dark:text-slate-500 dark:hover:text-slate-300;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg,
|
||||
.flatpickr-months .flatpickr-next-month svg {
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||
@apply fill-current;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
@apply text-slate-800 dark:text-slate-100;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.flatpickr-current-month {
|
||||
@apply text-sm font-medium;
|
||||
position: static;
|
||||
height: auto;
|
||||
width: auto;
|
||||
left: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month {
|
||||
@apply font-medium m-0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year {
|
||||
font-weight: inherit;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.numInputWrapper:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.numInputWrapper span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.flatpickr-weekday {
|
||||
@apply text-slate-400 dark:text-slate-500 font-medium text-xs;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop::before,
|
||||
.flatpickr-calendar.arrowTop::after,
|
||||
.flatpickr-calendar.arrowBottom::before,
|
||||
.flatpickr-calendar.arrowBottom::after {
|
||||
display: none;
|
||||
}
|
||||
142
src/css/additional-styles/utility-patterns.css
Normal file
142
src/css/additional-styles/utility-patterns.css
Normal file
@@ -0,0 +1,142 @@
|
||||
/* Typography */
|
||||
.h1 {
|
||||
@apply text-4xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-3xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply text-3xl font-extrabold;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply text-2xl font-extrabold tracking-tight;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn,
|
||||
.btn-lg,
|
||||
.btn-sm,
|
||||
.btn-xs {
|
||||
@apply font-medium text-sm inline-flex items-center justify-center border border-transparent rounded leading-5 shadow-sm transition duration-150 ease-in-out;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
@apply px-4 py-3;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply px-2 py-1;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
@apply px-2 py-0.5;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply bg-white dark:bg-slate-900/30 border focus:ring-0 focus:ring-offset-0 dark:disabled:bg-slate-700/30 dark:disabled:border-slate-700 dark:disabled:hover:border-slate-700;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select {
|
||||
@apply text-sm text-slate-800 dark:text-slate-100 leading-5 py-2 px-3 border-slate-200 hover:border-slate-300 focus:border-slate-300 dark:border-slate-700 dark:hover:border-slate-600 dark:focus:border-slate-600 shadow-sm;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
@apply placeholder-slate-400 dark:placeholder-slate-500;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply pr-10;
|
||||
}
|
||||
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-indigo-500 checked:bg-indigo-500 dark:checked:border-transparent border border-slate-300 focus:border-indigo-300 dark:border-slate-700 dark:focus:border-indigo-500/50;
|
||||
}
|
||||
|
||||
/* Switch element */
|
||||
.form-switch {
|
||||
@apply relative select-none;
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.form-switch label {
|
||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
||||
}
|
||||
|
||||
.form-switch label > span:first-child {
|
||||
@apply absolute block rounded-full;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 50%;
|
||||
transition: all .15s ease-out;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:checked + label {
|
||||
@apply bg-indigo-500;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:checked + label > span:first-child {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:disabled + label {
|
||||
@apply cursor-not-allowed bg-slate-100 dark:bg-slate-700/20 border border-slate-200 dark:border-slate-700;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:disabled + label > span:first-child {
|
||||
@apply bg-slate-400 dark:bg-slate-600;
|
||||
}
|
||||
|
||||
/* Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
10
src/css/style.css
Normal file
10
src/css/style.css
Normal file
@@ -0,0 +1,10 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback');
|
||||
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
|
||||
/* Additional styles */
|
||||
@import 'additional-styles/utility-patterns.css';
|
||||
@import 'additional-styles/flatpickr.css';
|
||||
|
||||
@import 'tailwindcss/utilities';
|
||||
70
src/css/tailwind.config.js
Normal file
70
src/css/tailwind.config.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
boxShadow: {
|
||||
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.02)',
|
||||
md: '0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.02)',
|
||||
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.01)',
|
||||
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.01)',
|
||||
},
|
||||
outline: {
|
||||
blue: '2px solid rgba(0, 112, 244, 0.5)',
|
||||
},
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'sans-serif'],
|
||||
},
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1.5' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.5715' }],
|
||||
base: ['1rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
'3xl': ['1.88rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '1.25', letterSpacing: '-0.02em' }],
|
||||
'5xl': ['3rem', { lineHeight: '1.25', letterSpacing: '-0.02em' }],
|
||||
'6xl': ['3.75rem', { lineHeight: '1.2', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
screens: {
|
||||
xs: '480px',
|
||||
},
|
||||
borderWidth: {
|
||||
3: '3px',
|
||||
},
|
||||
minWidth: {
|
||||
36: '9rem',
|
||||
44: '11rem',
|
||||
56: '14rem',
|
||||
60: '15rem',
|
||||
72: '18rem',
|
||||
80: '20rem',
|
||||
},
|
||||
maxWidth: {
|
||||
'8xl': '88rem',
|
||||
'9xl': '96rem',
|
||||
},
|
||||
zIndex: {
|
||||
60: '60',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
// eslint-disable-next-line global-require
|
||||
require('@tailwindcss/forms'),
|
||||
// add custom variant for expanding sidebar
|
||||
plugin(({ addVariant, e }) => {
|
||||
addVariant('sidebar-expanded', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ className }) => `.sidebar-expanded .${e(`sidebar-expanded${separator}${className}`)}`);
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
BIN
src/images/user-avatar-32.png
Normal file
BIN
src/images/user-avatar-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
9
src/main.js
Normal file
9
src/main.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import './css/style.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
49
src/pages/About.vue
Normal file
49
src/pages/About.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Sidebar :sidebarOpen="sidebarOpen" @close-sidebar="sidebarOpen = false" />
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
|
||||
<!-- Site header -->
|
||||
<Header :sidebarOpen="sidebarOpen" @toggle-sidebar="sidebarOpen = !sidebarOpen" />
|
||||
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<div>
|
||||
<h1>About</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Sidebar from '../partials/Sidebar.vue'
|
||||
import Header from '../partials/Header.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Sidebar,
|
||||
Header,
|
||||
|
||||
},
|
||||
setup() {
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
return {
|
||||
sidebarOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
49
src/pages/Dashboard.vue
Normal file
49
src/pages/Dashboard.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Sidebar :sidebarOpen="sidebarOpen" @close-sidebar="sidebarOpen = false" />
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
|
||||
<!-- Site header -->
|
||||
<Header :sidebarOpen="sidebarOpen" @toggle-sidebar="sidebarOpen = !sidebarOpen" />
|
||||
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Sidebar from '../partials/Sidebar.vue'
|
||||
import Header from '../partials/Header.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Sidebar,
|
||||
Header,
|
||||
|
||||
},
|
||||
setup() {
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
return {
|
||||
sidebarOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
49
src/pages/Keys.vue
Normal file
49
src/pages/Keys.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Sidebar :sidebarOpen="sidebarOpen" @close-sidebar="sidebarOpen = false" />
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
|
||||
<!-- Site header -->
|
||||
<Header :sidebarOpen="sidebarOpen" @toggle-sidebar="sidebarOpen = !sidebarOpen" />
|
||||
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<div>
|
||||
<h1>Keys</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Sidebar from '../partials/Sidebar.vue'
|
||||
import Header from '../partials/Header.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Sidebar,
|
||||
Header,
|
||||
|
||||
},
|
||||
setup() {
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
return {
|
||||
sidebarOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
93
src/pages/Signin.vue
Normal file
93
src/pages/Signin.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<main class="bg-white dark:bg-slate-900">
|
||||
|
||||
<div class="relative flex">
|
||||
|
||||
<!-- Content -->
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="min-h-[100dvh] h-full flex flex-col after:flex-1">
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
<!-- Logo -->
|
||||
<router-link class="block" to="/">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient x1="28.538%" y1="20.229%" x2="100%" y2="108.156%" id="logo-a">
|
||||
<stop stop-color="#A5B4FC" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#A5B4FC" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="88.638%" y1="29.267%" x2="22.42%" y2="100%" id="logo-b">
|
||||
<stop stop-color="#38BDF8" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#38BDF8" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="#6366F1" width="32" height="32" rx="16" />
|
||||
<path d="M18.277.16C26.035 1.267 32 7.938 32 16c0 8.837-7.163 16-16 16a15.937 15.937 0 01-10.426-3.863L18.277.161z" fill="#4F46E5" />
|
||||
<path d="M7.404 2.503l18.339 26.19A15.93 15.93 0 0116 32C7.163 32 0 24.837 0 16 0 10.327 2.952 5.344 7.404 2.503z" fill="url(#logo-a)" />
|
||||
<path d="M2.223 24.14L29.777 7.86A15.926 15.926 0 0132 16c0 8.837-7.163 16-16 16-5.864 0-10.991-3.154-13.777-7.86z" fill="url(#logo-b)" />
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-sm mx-auto w-full px-4 py-8">
|
||||
<h1 class="text-3xl text-slate-800 dark:text-slate-100 font-bold mb-6">Welcome back! ✨</h1>
|
||||
<!-- Form -->
|
||||
<form>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="email">Email Address</label>
|
||||
<input id="email" class="form-input w-full" type="email" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="password">Password</label>
|
||||
<input id="password" class="form-input w-full" type="password" autoComplete="on" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-6">
|
||||
<div class="mr-1">
|
||||
<router-link class="text-sm underline hover:no-underline" to="/reset-password">Forgot Password?</router-link>
|
||||
</div>
|
||||
<router-link class="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-3" to="/">Sign In</router-link>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Footer -->
|
||||
<div class="pt-5 mt-6 border-t border-slate-200 dark:border-slate-700">
|
||||
<div class="text-sm">
|
||||
Don’t you have an account? <router-link class="font-medium text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400" to="/signup">Sign Up</router-link>
|
||||
</div>
|
||||
<!-- Warning -->
|
||||
<div class="mt-5">
|
||||
<div class="bg-amber-100 dark:bg-amber-400/30 text-amber-600 dark:text-amber-400 px-3 py-2 rounded">
|
||||
<svg class="inline w-3 h-3 shrink-0 fill-current mr-2" viewBox="0 0 12 12">
|
||||
<path d="M10.28 1.28L3.989 7.575 1.695 5.28A1 1 0 00.28 6.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 1.28z" />
|
||||
</svg>
|
||||
<span class="text-sm">
|
||||
To support you during the pandemic super pro features are free until March 31st.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image -->
|
||||
<div class="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<img class="object-cover object-center w-full h-full" src="../images/auth-image.jpg" width="760" height="1024" alt="Authentication" />
|
||||
<img class="absolute top-1/4 left-0 -translate-x-1/2 ml-8 hidden lg:block" src="../images/auth-decoration.png" width="218" height="224" alt="Authentication decoration" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Signin',
|
||||
}
|
||||
</script>
|
||||
97
src/pages/Signup.vue
Normal file
97
src/pages/Signup.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<main class="bg-white dark:bg-slate-900">
|
||||
|
||||
<div class="relative flex">
|
||||
|
||||
<!-- Content -->
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="min-h-[100dvh] h-full flex flex-col after:flex-1">
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
<!-- Logo -->
|
||||
<router-link class="block" to="/">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient x1="28.538%" y1="20.229%" x2="100%" y2="108.156%" id="logo-a">
|
||||
<stop stop-color="#A5B4FC" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#A5B4FC" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="88.638%" y1="29.267%" x2="22.42%" y2="100%" id="logo-b">
|
||||
<stop stop-color="#38BDF8" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#38BDF8" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="#6366F1" width="32" height="32" rx="16" />
|
||||
<path d="M18.277.16C26.035 1.267 32 7.938 32 16c0 8.837-7.163 16-16 16a15.937 15.937 0 01-10.426-3.863L18.277.161z" fill="#4F46E5" />
|
||||
<path d="M7.404 2.503l18.339 26.19A15.93 15.93 0 0116 32C7.163 32 0 24.837 0 16 0 10.327 2.952 5.344 7.404 2.503z" fill="url(#logo-a)" />
|
||||
<path d="M2.223 24.14L29.777 7.86A15.926 15.926 0 0132 16c0 8.837-7.163 16-16 16-5.864 0-10.991-3.154-13.777-7.86z" fill="url(#logo-b)" />
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-sm mx-auto w-full px-4 py-8">
|
||||
<h1 class="text-3xl text-slate-800 dark:text-slate-100 font-bold mb-6">Create your Account ✨</h1>
|
||||
<!-- Form -->
|
||||
<form>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="email">Email Address <span class="text-rose-500">*</span></label>
|
||||
<input id="email" class="form-input w-full" type="email" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="name">Full Name <span class="text-rose-500">*</span></label>
|
||||
<input id="name" class="form-input w-full" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="role">Your Role <span class="text-rose-500">*</span></label>
|
||||
<select id="role" class="form-select w-full">
|
||||
<option>Designer</option>
|
||||
<option>Developer</option>
|
||||
<option>Accountant</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1" for="password">Password</label>
|
||||
<input id="password" class="form-input w-full" type="password" autoComplete="on" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-6">
|
||||
<div class="mr-1">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="form-checkbox" />
|
||||
<span class="text-sm ml-2">Email me about product news.</span>
|
||||
</label>
|
||||
</div>
|
||||
<router-link class="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-3 whitespace-nowrap" to="/">Sign Up</router-link>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Footer -->
|
||||
<div class="pt-5 mt-6 border-t border-slate-200 dark:border-slate-700">
|
||||
<div class="text-sm">
|
||||
Have an account? <router-link class="font-medium text-indigo-500 hover:text-indigo-600 dark:hover:text-indigo-400" to="/signin">Sign In</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image -->
|
||||
<div class="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<img class="object-cover object-center w-full h-full" src="../images/auth-image.jpg" width="760" height="1024" alt="Authentication" />
|
||||
<img class="absolute top-1/4 left-0 -translate-x-1/2 ml-8 hidden lg:block" src="../images/auth-decoration.png" width="218" height="224" alt="Authentication decoration" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Signup',
|
||||
}
|
||||
</script>
|
||||
49
src/pages/Usages.vue
Normal file
49
src/pages/Usages.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Sidebar :sidebarOpen="sidebarOpen" @close-sidebar="sidebarOpen = false" />
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
|
||||
<!-- Site header -->
|
||||
<Header :sidebarOpen="sidebarOpen" @toggle-sidebar="sidebarOpen = !sidebarOpen" />
|
||||
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<div>
|
||||
<h1>Usages</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Sidebar from '../partials/Sidebar.vue'
|
||||
import Header from '../partials/Header.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Sidebar,
|
||||
Header,
|
||||
|
||||
},
|
||||
setup() {
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
return {
|
||||
sidebarOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
49
src/pages/Users.vue
Normal file
49
src/pages/Users.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Sidebar :sidebarOpen="sidebarOpen" @close-sidebar="sidebarOpen = false" />
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
|
||||
<!-- Site header -->
|
||||
<Header :sidebarOpen="sidebarOpen" @toggle-sidebar="sidebarOpen = !sidebarOpen" />
|
||||
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<div>
|
||||
<h1>Users</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Sidebar from '../partials/Sidebar.vue'
|
||||
import Header from '../partials/Header.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Sidebar,
|
||||
Header,
|
||||
|
||||
},
|
||||
setup() {
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
return {
|
||||
sidebarOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
78
src/partials/Header copy.vue
Normal file
78
src/partials/Header copy.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<header class="sticky top-0 bg-white dark:bg-[#182235] border-b border-slate-200 dark:border-slate-700 z-30">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16 -mb-px">
|
||||
|
||||
<!-- Header: Left side -->
|
||||
<div class="flex">
|
||||
|
||||
<!-- Hamburger button -->
|
||||
<button class="text-slate-500 hover:text-slate-600 lg:hidden" @click.stop="$emit('toggle-sidebar')" aria-controls="sidebar" :aria-expanded="sidebarOpen">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="4" y="5" width="16" height="2" />
|
||||
<rect x="4" y="11" width="16" height="2" />
|
||||
<rect x="4" y="17" width="16" height="2" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Header: Right side -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<div>
|
||||
<button
|
||||
class="w-8 h-8 flex items-center justify-center bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600/80 rounded-full ml-3"
|
||||
:class="{ 'bg-slate-200': searchModalOpen }"
|
||||
@click.stop="searchModalOpen = true"
|
||||
aria-controls="search-modal"
|
||||
>
|
||||
<span class="sr-only">Search</span>
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-current text-slate-500 dark:text-slate-400" d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" />
|
||||
<path class="fill-current text-slate-400 dark:text-slate-500" d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z" />
|
||||
</svg>
|
||||
</button>
|
||||
<SearchModal id="search-modal" searchId="search" :modalOpen="searchModalOpen" @open-modal="searchModalOpen = true" @close-modal="searchModalOpen = false" />
|
||||
</div>
|
||||
<Notifications align="right" />
|
||||
<Help align="right" />
|
||||
<ThemeToggle />
|
||||
<!-- Divider -->
|
||||
<hr class="w-px h-6 bg-slate-200 dark:bg-slate-700 border-none" />
|
||||
<UserMenu align="right" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
import SearchModal from '../components/ModalSearch.vue'
|
||||
import Notifications from '../components/DropdownNotifications.vue'
|
||||
import Help from '../components/DropdownHelp.vue'
|
||||
import ThemeToggle from '../components/ThemeToggle.vue'
|
||||
import UserMenu from '../components/DropdownProfile.vue'
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
props: ['sidebarOpen'],
|
||||
components: {
|
||||
SearchModal,
|
||||
Notifications,
|
||||
Help,
|
||||
ThemeToggle,
|
||||
UserMenu,
|
||||
},
|
||||
setup() {
|
||||
const searchModalOpen = ref(false)
|
||||
return {
|
||||
searchModalOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
61
src/partials/Header.vue
Normal file
61
src/partials/Header.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<header class="sticky top-0 bg-white dark:bg-[#182235] border-b border-slate-200 dark:border-slate-700 z-30">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16 -mb-px">
|
||||
|
||||
<!-- Header: Left side -->
|
||||
<div class="flex">
|
||||
|
||||
<!-- Hamburger button -->
|
||||
<button class="text-slate-500 hover:text-slate-600 lg:hidden" @click.stop="$emit('toggle-sidebar')" aria-controls="sidebar" :aria-expanded="sidebarOpen">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="4" y="5" width="16" height="2" />
|
||||
<rect x="4" y="11" width="16" height="2" />
|
||||
<rect x="4" y="17" width="16" height="2" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Header: Right side -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<ThemeToggle />
|
||||
<!-- Divider -->
|
||||
<hr class="w-px h-6 bg-slate-200 dark:bg-slate-700 border-none" />
|
||||
<UserMenu align="right" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// import SearchModal from '../components/ModalSearch.vue'
|
||||
// import Notifications from '../components/DropdownNotifications.vue'
|
||||
// import Help from '../components/DropdownHelp.vue'
|
||||
import ThemeToggle from '../components/ThemeToggle.vue'
|
||||
import UserMenu from '../components/DropdownProfile.vue'
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
props: ['sidebarOpen'],
|
||||
components: {
|
||||
// SearchModal,
|
||||
// Notifications,
|
||||
// Help,
|
||||
ThemeToggle,
|
||||
UserMenu,
|
||||
},
|
||||
setup() {
|
||||
const searchModalOpen = ref(false)
|
||||
return {
|
||||
searchModalOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
242
src/partials/Sidebar copy 2.vue
Normal file
242
src/partials/Sidebar copy 2.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="min-w-fit">
|
||||
<!-- Sidebar backdrop (mobile only) -->
|
||||
<div class="fixed inset-0 bg-slate-900 bg-opacity-30 z-40 lg:hidden lg:z-auto transition-opacity duration-200" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'" aria-hidden="true"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
id="sidebar"
|
||||
ref="sidebar"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-slate-800 p-4 transition-all duration-200 ease-in-out"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-64'"
|
||||
>
|
||||
|
||||
<!-- Sidebar header -->
|
||||
<div class="flex justify-between mb-10 pr-3 sm:px-2">
|
||||
<!-- Close button -->
|
||||
<button
|
||||
ref="trigger"
|
||||
class="lg:hidden text-slate-500 hover:text-slate-400"
|
||||
@click.stop="$emit('close-sidebar')"
|
||||
aria-controls="sidebar"
|
||||
:aria-expanded="sidebarOpen"
|
||||
>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Logo -->
|
||||
<router-link class="block" to="/">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient x1="28.538%" y1="20.229%" x2="100%" y2="108.156%" id="logo-a">
|
||||
<stop stop-color="#A5B4FC" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#A5B4FC" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="88.638%" y1="29.267%" x2="22.42%" y2="100%" id="logo-b">
|
||||
<stop stop-color="#38BDF8" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#38BDF8" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="#6366F1" width="32" height="32" rx="16" />
|
||||
<path d="M18.277.16C26.035 1.267 32 7.938 32 16c0 8.837-7.163 16-16 16a15.937 15.937 0 01-10.426-3.863L18.277.161z" fill="#4F46E5" />
|
||||
<path d="M7.404 2.503l18.339 26.19A15.93 15.93 0 0116 32C7.163 32 0 24.837 0 16 0 10.327 2.952 5.344 7.404 2.503z" fill="url(#logo-a)" />
|
||||
<path d="M2.223 24.14L29.777 7.86A15.926 15.926 0 0132 16c0 8.837-7.163 16-16 16-5.864 0-10.991-3.154-13.777-7.86z" fill="url(#logo-b)" />
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="space-y-8">
|
||||
<!-- Pages group -->
|
||||
<div>
|
||||
<h3 class="text-xs uppercase text-slate-500 font-semibold pl-3">
|
||||
<span class="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6" aria-hidden="true">•••</span>
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">Pages</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<!-- Dashboard -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-500' : 'text-slate-400'" d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-600' : 'text-slate-600'" d="M12 3c-4.963 0-9 4.037-9 9s4.037 9 9 9 9-4.037 9-9-4.037-9-9-9z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-200' : 'text-slate-400'" d="M12 15c-1.654 0-3-1.346-3-3 0-.462.113-.894.3-1.285L6 6l4.714 3.301A2.973 2.973 0 0112 9c1.654 0 3 1.346 3 3s-1.346 3-3 3z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Dashboard</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Main</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/analytics" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Analytics</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/fintech" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Fintech</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
|
||||
<!-- Messages -->
|
||||
<router-link to="/messages" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M14.5 7c4.695 0 8.5 3.184 8.5 7.111 0 1.597-.638 3.067-1.7 4.253V23l-4.108-2.148a10 10 0 01-2.692.37c-4.695 0-8.5-3.184-8.5-7.11C6 10.183 9.805 7 14.5 7z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M11 1C5.477 1 1 4.582 1 9c0 1.797.75 3.45 2 4.785V19l4.833-2.416C8.829 16.85 9.892 17 11 17c5.523 0 10-3.582 10-8s-4.477-8-10-8z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Messages</span>
|
||||
</div>
|
||||
<!-- Badge -->
|
||||
<div class="flex flex-shrink-0 ml-2">
|
||||
<span class="inline-flex items-center justify-center h-5 text-xs font-medium text-white bg-indigo-500 px-2 rounded">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- About -->
|
||||
<router-link to="/about" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M16 13v4H8v-4H0l3-9h18l3 9h-8Z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="m23.72 12 .229.686A.984.984 0 0 1 24 13v8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-8c0-.107.017-.213.051-.314L.28 12H8v4h8v-4H23.72ZM13 0v7h3l-4 5-4-5h3V0h2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">About</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Calendar -->
|
||||
<router-link to="/calendar" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M1 3h22v20H1z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M21 3h2v4H1V3h2V1h4v2h10V1h4v2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Calendar</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expand / collapse button -->
|
||||
<div class="pt-3 hidden lg:inline-flex 2xl:hidden justify-end mt-auto">
|
||||
<div class="px-3 py-2">
|
||||
<button @click.prevent="sidebarExpanded = !sidebarExpanded">
|
||||
<span class="sr-only">Expand / collapse sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current sidebar-expanded:rotate-180" viewBox="0 0 24 24">
|
||||
<path class="text-slate-400" d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z" />
|
||||
<path class="text-slate-600" d="M3 23H1V1h2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import SidebarLinkGroup from './SidebarLinkGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'Sidebar',
|
||||
props: ['sidebarOpen'],
|
||||
components: {
|
||||
SidebarLinkGroup,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const trigger = ref(null)
|
||||
const sidebar = ref(null)
|
||||
|
||||
const storedSidebarExpanded = localStorage.getItem('sidebar-expanded')
|
||||
const sidebarExpanded = ref(storedSidebarExpanded === null ? false : storedSidebarExpanded === 'true')
|
||||
|
||||
const currentRoute = useRouter().currentRoute.value
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!sidebar.value || !trigger.value) return
|
||||
if (
|
||||
!props.sidebarOpen ||
|
||||
sidebar.value.contains(target) ||
|
||||
trigger.value.contains(target)
|
||||
) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.sidebarOpen || keyCode !== 27) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
watch(sidebarExpanded, () => {
|
||||
localStorage.setItem('sidebar-expanded', sidebarExpanded.value)
|
||||
if (sidebarExpanded.value) {
|
||||
document.querySelector('body').classList.add('sidebar-expanded')
|
||||
} else {
|
||||
document.querySelector('body').classList.remove('sidebar-expanded')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
trigger,
|
||||
sidebar,
|
||||
sidebarExpanded,
|
||||
currentRoute,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
917
src/partials/Sidebar copy.vue
Normal file
917
src/partials/Sidebar copy.vue
Normal file
@@ -0,0 +1,917 @@
|
||||
<template>
|
||||
<div class="min-w-fit">
|
||||
<!-- Sidebar backdrop (mobile only) -->
|
||||
<div class="fixed inset-0 bg-slate-900 bg-opacity-30 z-40 lg:hidden lg:z-auto transition-opacity duration-200" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'" aria-hidden="true"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
id="sidebar"
|
||||
ref="sidebar"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-slate-800 p-4 transition-all duration-200 ease-in-out"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-64'"
|
||||
>
|
||||
|
||||
<!-- Sidebar header -->
|
||||
<div class="flex justify-between mb-10 pr-3 sm:px-2">
|
||||
<!-- Close button -->
|
||||
<button
|
||||
ref="trigger"
|
||||
class="lg:hidden text-slate-500 hover:text-slate-400"
|
||||
@click.stop="$emit('close-sidebar')"
|
||||
aria-controls="sidebar"
|
||||
:aria-expanded="sidebarOpen"
|
||||
>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Logo -->
|
||||
<router-link class="block" to="/">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient x1="28.538%" y1="20.229%" x2="100%" y2="108.156%" id="logo-a">
|
||||
<stop stop-color="#A5B4FC" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#A5B4FC" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="88.638%" y1="29.267%" x2="22.42%" y2="100%" id="logo-b">
|
||||
<stop stop-color="#38BDF8" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#38BDF8" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="#6366F1" width="32" height="32" rx="16" />
|
||||
<path d="M18.277.16C26.035 1.267 32 7.938 32 16c0 8.837-7.163 16-16 16a15.937 15.937 0 01-10.426-3.863L18.277.161z" fill="#4F46E5" />
|
||||
<path d="M7.404 2.503l18.339 26.19A15.93 15.93 0 0116 32C7.163 32 0 24.837 0 16 0 10.327 2.952 5.344 7.404 2.503z" fill="url(#logo-a)" />
|
||||
<path d="M2.223 24.14L29.777 7.86A15.926 15.926 0 0132 16c0 8.837-7.163 16-16 16-5.864 0-10.991-3.154-13.777-7.86z" fill="url(#logo-b)" />
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="space-y-8">
|
||||
<!-- Pages group -->
|
||||
<div>
|
||||
<h3 class="text-xs uppercase text-slate-500 font-semibold pl-3">
|
||||
<span class="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6" aria-hidden="true">•••</span>
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">Pages</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<!-- Dashboard -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-500' : 'text-slate-400'" d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-600' : 'text-slate-600'" d="M12 3c-4.963 0-9 4.037-9 9s4.037 9 9 9 9-4.037 9-9-4.037-9-9-9z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-200' : 'text-slate-400'" d="M12 15c-1.654 0-3-1.346-3-3 0-.462.113-.894.3-1.285L6 6l4.714 3.301A2.973 2.973 0 0112 9c1.654 0 3 1.346 3 3s-1.346 3-3 3z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Dashboard</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Main</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/analytics" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Analytics</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/fintech" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Fintech</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- E-Commerce -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('ecommerce')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('ecommerce') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('ecommerce') ? 'text-indigo-300' : 'text-slate-400'" d="M13 15l11-7L11.504.136a1 1 0 00-1.019.007L0 7l13 8z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('ecommerce') ? 'text-indigo-600' : 'text-slate-700'" d="M13 15L0 7v9c0 .355.189.685.496.864L13 24v-9z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('ecommerce') ? 'text-indigo-500' : 'text-slate-600'" d="M13 15.047V24l10.573-7.181A.999.999 0 0024 16V8l-11 7.047z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">E-Commerce </span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/ecommerce/customers" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Customers</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/orders" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Orders</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/invoices" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Invoices</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/shop" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Shop</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/shop-2" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Shop 2</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/product" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Single Product</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/cart" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Cart</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/cart-2" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Cart 2</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/cart-3" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Cart 3</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/ecommerce/pay" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Pay</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Community -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('community')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('community') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('community') ? 'text-indigo-500' : 'text-slate-600'" d="M18.974 8H22a2 2 0 012 2v6h-2v5a1 1 0 01-1 1h-2a1 1 0 01-1-1v-5h-2v-6a2 2 0 012-2h.974zM20 7a2 2 0 11-.001-3.999A2 2 0 0120 7zM2.974 8H6a2 2 0 012 2v6H6v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5H0v-6a2 2 0 012-2h.974zM4 7a2 2 0 11-.001-3.999A2 2 0 014 7z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('community') ? 'text-indigo-300' : 'text-slate-400'" d="M12 6a3 3 0 110-6 3 3 0 010 6zm2 18h-4a1 1 0 01-1-1v-6H6v-6a3 3 0 013-3h6a3 3 0 013 3v6h-3v6a1 1 0 01-1 1z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Community</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/community/users-tabs" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Users - Tabs</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/users-tiles" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Users - Tiles</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/profile" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Profile</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/feed" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Feed</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/forum" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Forum</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/forum-post" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Forum - Post</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/meetups" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Meetups</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/community/meetups-post" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Meetups - Post</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Finance -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('finance')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('finance') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('finance') ? 'text-indigo-300' : 'text-slate-400'" d="M13 6.068a6.035 6.035 0 0 1 4.932 4.933H24c-.486-5.846-5.154-10.515-11-11v6.067Z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('finance') ? 'text-indigo-500' : 'text-slate-700'" d="M18.007 13c-.474 2.833-2.919 5-5.864 5a5.888 5.888 0 0 1-3.694-1.304L4 20.731C6.131 22.752 8.992 24 12.143 24c6.232 0 11.35-4.851 11.857-11h-5.993Z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('finance') ? 'text-indigo-600' : 'text-slate-600'" d="M6.939 15.007A5.861 5.861 0 0 1 6 11.829c0-2.937 2.167-5.376 5-5.85V0C4.85.507 0 5.614 0 11.83c0 2.695.922 5.174 2.456 7.17l4.483-3.993Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Finance</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/finance/cards" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Cards</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/finance/transactions" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Transactions</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/finance/transaction-details" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Transaction Details</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Job Board -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('job')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('job') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('job') ? 'text-indigo-600' : 'text-slate-700'" d="M4.418 19.612A9.092 9.092 0 0 1 2.59 17.03L.475 19.14c-.848.85-.536 2.395.743 3.673a4.413 4.413 0 0 0 1.677 1.082c.253.086.519.131.787.135.45.011.886-.16 1.208-.474L7 21.44a8.962 8.962 0 0 1-2.582-1.828Z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('job') ? 'text-indigo-500' : 'text-slate-600'" d="M10.034 13.997a11.011 11.011 0 0 1-2.551-3.862L4.595 13.02a2.513 2.513 0 0 0-.4 2.645 6.668 6.668 0 0 0 1.64 2.532 5.525 5.525 0 0 0 3.643 1.824 2.1 2.1 0 0 0 1.534-.587l2.883-2.882a11.156 11.156 0 0 1-3.861-2.556Z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('job') ? 'text-indigo-300' : 'text-slate-400'" d="M21.554 2.471A8.958 8.958 0 0 0 18.167.276a3.105 3.105 0 0 0-3.295.467L9.715 5.888c-1.41 1.408-.665 4.275 1.733 6.668a8.958 8.958 0 0 0 3.387 2.196c.459.157.94.24 1.425.246a2.559 2.559 0 0 0 1.87-.715l5.156-5.146c1.415-1.406.666-4.273-1.732-6.666Zm.318 5.257c-.148.147-.594.2-1.256-.018A7.037 7.037 0 0 1 18.016 6c-1.73-1.728-2.104-3.475-1.73-3.845a.671.671 0 0 1 .465-.129c.27.008.536.057.79.146a7.07 7.07 0 0 1 2.6 1.711c1.73 1.73 2.105 3.472 1.73 3.846Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Job Board</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/job/job-listing" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Listing</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/job/job-post" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Job Post</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/job/company-profile" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Company Profile</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Tasks -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('tasks')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('tasks') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('tasks') ? 'text-indigo-500' : 'text-slate-600'" d="M8 1v2H3v19h18V3h-5V1h7v23H1V1z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('tasks') ? 'text-indigo-500' : 'text-slate-600'" d="M1 1h22v23H1z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('tasks') ? 'text-indigo-300' : 'text-slate-400'" d="M15 10.586L16.414 12 11 17.414 7.586 14 9 12.586l2 2zM5 0h14v4H5z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Tasks</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/tasks/kanban" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Kanban</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/tasks/list" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">List</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Messages -->
|
||||
<router-link to="/messages" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M14.5 7c4.695 0 8.5 3.184 8.5 7.111 0 1.597-.638 3.067-1.7 4.253V23l-4.108-2.148a10 10 0 01-2.692.37c-4.695 0-8.5-3.184-8.5-7.11C6 10.183 9.805 7 14.5 7z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M11 1C5.477 1 1 4.582 1 9c0 1.797.75 3.45 2 4.785V19l4.833-2.416C8.829 16.85 9.892 17 11 17c5.523 0 10-3.582 10-8s-4.477-8-10-8z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Messages</span>
|
||||
</div>
|
||||
<!-- Badge -->
|
||||
<div class="flex flex-shrink-0 ml-2">
|
||||
<span class="inline-flex items-center justify-center h-5 text-xs font-medium text-white bg-indigo-500 px-2 rounded">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Inbox -->
|
||||
<router-link to="/inbox" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M16 13v4H8v-4H0l3-9h18l3 9h-8Z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="m23.72 12 .229.686A.984.984 0 0 1 24 13v8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-8c0-.107.017-.213.051-.314L.28 12H8v4h8v-4H23.72ZM13 0v7h3l-4 5-4-5h3V0h2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Inbox</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Calendar -->
|
||||
<router-link to="/calendar" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M1 3h22v20H1z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M21 3h2v4H1V3h2V1h4v2h10V1h4v2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Calendar</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Campaigns -->
|
||||
<router-link to="/campaigns" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M20 7a.75.75 0 01-.75-.75 1.5 1.5 0 00-1.5-1.5.75.75 0 110-1.5 1.5 1.5 0 001.5-1.5.75.75 0 111.5 0 1.5 1.5 0 001.5 1.5.75.75 0 110 1.5 1.5 1.5 0 00-1.5 1.5A.75.75 0 0120 7zM4 23a.75.75 0 01-.75-.75 1.5 1.5 0 00-1.5-1.5.75.75 0 110-1.5 1.5 1.5 0 001.5-1.5.75.75 0 111.5 0 1.5 1.5 0 001.5 1.5.75.75 0 110 1.5 1.5 1.5 0 00-1.5 1.5A.75.75 0 014 23z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M17 23a1 1 0 01-1-1 4 4 0 00-4-4 1 1 0 010-2 4 4 0 004-4 1 1 0 012 0 4 4 0 004 4 1 1 0 010 2 4 4 0 00-4 4 1 1 0 01-1 1zM7 13a1 1 0 01-1-1 4 4 0 00-4-4 1 1 0 110-2 4 4 0 004-4 1 1 0 112 0 4 4 0 004 4 1 1 0 010 2 4 4 0 00-4 4 1 1 0 01-1 1z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Campaigns</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Settings -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('settings')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('settings') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('settings') ? 'text-indigo-500' : 'text-slate-600'" d="M19.714 14.7l-7.007 7.007-1.414-1.414 7.007-7.007c-.195-.4-.298-.84-.3-1.286a3 3 0 113 3 2.969 2.969 0 01-1.286-.3z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('settings') ? 'text-indigo-300' : 'text-slate-400'" d="M10.714 18.3c.4-.195.84-.298 1.286-.3a3 3 0 11-3 3c.002-.446.105-.885.3-1.286l-6.007-6.007 1.414-1.414 6.007 6.007z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('settings') ? 'text-indigo-500' : 'text-slate-600'" d="M5.7 10.714c.195.4.298.84.3 1.286a3 3 0 11-3-3c.446.002.885.105 1.286.3l7.007-7.007 1.414 1.414L5.7 10.714z" />
|
||||
<path class="fill-current" :class="currentRoute.fullPath.includes('settings') ? 'text-indigo-300' : 'text-slate-400'" d="M19.707 9.292a3.012 3.012 0 00-1.415 1.415L13.286 5.7c-.4.195-.84.298-1.286.3a3 3 0 113-3 2.969 2.969 0 01-.3 1.286l5.007 5.006z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Settings</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/settings/account" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">My Account</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/settings/notifications" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">My Notifications</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/settings/apps" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Connected Apps</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/settings/plans" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Plans</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/settings/billing" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Billing & Invoices</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/settings/feedback" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Give Feedback</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Utility -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('utility')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('utility') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('utility') ? 'text-indigo-300' : 'text-slate-400'" cx="18.5" cy="5.5" r="4.5" />
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('utility') ? 'text-indigo-500' : 'text-slate-600'" cx="5.5" cy="5.5" r="4.5" />
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('utility') ? 'text-indigo-500' : 'text-slate-600'" cx="18.5" cy="18.5" r="4.5" />
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('utility') ? 'text-indigo-300' : 'text-slate-400'" cx="5.5" cy="18.5" r="4.5" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Utility</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/utility/changelog" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Changelog</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/utility/roadmap" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Roadmap</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/utility/faqs" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">FAQs</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/utility/empty-state" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Empty State</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/utility/404" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">404</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/utility/knowledge-base" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Knowledge Base</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- More group -->
|
||||
<div>
|
||||
<h3 class="text-xs uppercase text-slate-500 font-semibold pl-3">
|
||||
<span class="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6" aria-hidden="true">•••</span>
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">More</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<!-- Authentication -->
|
||||
<SidebarLinkGroup v-slot="parentLink">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="parentLink.expanded ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current text-slate-600" d="M8.07 16H10V8H8.07a8 8 0 110 8z" />
|
||||
<path class="fill-current text-slate-400" d="M15 12L8 6v5H0v2h8v5z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Authentication</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/signin" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Sign in</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/signup" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Sign up</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/reset-password" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Reset Password</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Onboarding -->
|
||||
<SidebarLinkGroup v-slot="parentLink">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="parentLink.expanded ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current text-slate-600" d="M19 5h1v14h-2V7.414L5.707 19.707 5 19H4V5h2v11.586L18.293 4.293 19 5Z" />
|
||||
<path class="fill-current text-slate-400" d="M5 9a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm14 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8ZM5 23a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm14 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Onboarding</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/onboarding-01" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Step 1</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/onboarding-02" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Step 2</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/onboarding-03" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Step 3</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/onboarding-04" custom v-slot="{ href, navigate }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block text-slate-400 hover:text-slate-200 transition duration-150 truncate" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Step 4</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
<!-- Components -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('component')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="currentRoute.fullPath.includes('component') ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('component') ? 'text-indigo-500' : 'text-slate-600'" cx="16" cy="8" r="8" />
|
||||
<circle class="fill-current" :class="currentRoute.fullPath.includes('component') ? 'text-indigo-300' : 'text-slate-400'" cx="8" cy="16" r="8" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Components </span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/component/button" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Button</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/form" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Input Form</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/dropdown" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Dropdown</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/alert" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Alert & Banner</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/modal" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Modal</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/pagination" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Pagination</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/tabs" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Tabs</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/breadcrumb" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Breadcrumb</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/badge" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Badge</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/avatar" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Avatar</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/tooltip" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Tooltip</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/accordion" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Accordion</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/component/icons" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Icons</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expand / collapse button -->
|
||||
<div class="pt-3 hidden lg:inline-flex 2xl:hidden justify-end mt-auto">
|
||||
<div class="px-3 py-2">
|
||||
<button @click.prevent="sidebarExpanded = !sidebarExpanded">
|
||||
<span class="sr-only">Expand / collapse sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current sidebar-expanded:rotate-180" viewBox="0 0 24 24">
|
||||
<path class="text-slate-400" d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z" />
|
||||
<path class="text-slate-600" d="M3 23H1V1h2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import SidebarLinkGroup from './SidebarLinkGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'Sidebar',
|
||||
props: ['sidebarOpen'],
|
||||
components: {
|
||||
SidebarLinkGroup,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const trigger = ref(null)
|
||||
const sidebar = ref(null)
|
||||
|
||||
const storedSidebarExpanded = localStorage.getItem('sidebar-expanded')
|
||||
const sidebarExpanded = ref(storedSidebarExpanded === null ? false : storedSidebarExpanded === 'true')
|
||||
|
||||
const currentRoute = useRouter().currentRoute.value
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!sidebar.value || !trigger.value) return
|
||||
if (
|
||||
!props.sidebarOpen ||
|
||||
sidebar.value.contains(target) ||
|
||||
trigger.value.contains(target)
|
||||
) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.sidebarOpen || keyCode !== 27) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
watch(sidebarExpanded, () => {
|
||||
localStorage.setItem('sidebar-expanded', sidebarExpanded.value)
|
||||
if (sidebarExpanded.value) {
|
||||
document.querySelector('body').classList.add('sidebar-expanded')
|
||||
} else {
|
||||
document.querySelector('body').classList.remove('sidebar-expanded')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
trigger,
|
||||
sidebar,
|
||||
sidebarExpanded,
|
||||
currentRoute,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
282
src/partials/Sidebar.vue
Normal file
282
src/partials/Sidebar.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div class="min-w-fit">
|
||||
<!-- Sidebar backdrop (mobile only) -->
|
||||
<div class="fixed inset-0 bg-slate-900 bg-opacity-30 z-40 lg:hidden lg:z-auto transition-opacity duration-200" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'" aria-hidden="true"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
id="sidebar"
|
||||
ref="sidebar"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-slate-800 p-4 transition-all duration-200 ease-in-out"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-64'"
|
||||
>
|
||||
|
||||
<!-- Sidebar header -->
|
||||
<div class="flex justify-between mb-10 pr-3 sm:px-2">
|
||||
<!-- Close button -->
|
||||
<button
|
||||
ref="trigger"
|
||||
class="lg:hidden text-slate-500 hover:text-slate-400"
|
||||
@click.stop="$emit('close-sidebar')"
|
||||
aria-controls="sidebar"
|
||||
:aria-expanded="sidebarOpen"
|
||||
>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Logo -->
|
||||
<router-link class="block" to="/">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient x1="28.538%" y1="20.229%" x2="100%" y2="108.156%" id="logo-a">
|
||||
<stop stop-color="#A5B4FC" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#A5B4FC" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="88.638%" y1="29.267%" x2="22.42%" y2="100%" id="logo-b">
|
||||
<stop stop-color="#38BDF8" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#38BDF8" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="#6366F1" width="32" height="32" rx="16" />
|
||||
<path d="M18.277.16C26.035 1.267 32 7.938 32 16c0 8.837-7.163 16-16 16a15.937 15.937 0 01-10.426-3.863L18.277.161z" fill="#4F46E5" />
|
||||
<path d="M7.404 2.503l18.339 26.19A15.93 15.93 0 0116 32C7.163 32 0 24.837 0 16 0 10.327 2.952 5.344 7.404 2.503z" fill="url(#logo-a)" />
|
||||
<path d="M2.223 24.14L29.777 7.86A15.926 15.926 0 0132 16c0 8.837-7.163 16-16 16-5.864 0-10.991-3.154-13.777-7.86z" fill="url(#logo-b)" />
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="space-y-8">
|
||||
<!-- Pages group -->
|
||||
<div>
|
||||
<h3 class="text-xs uppercase text-slate-500 font-semibold pl-3">
|
||||
<span class="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6" aria-hidden="true">•••</span>
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">Pages</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<!-- Dashboard -->
|
||||
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'hover:text-slate-200' : 'hover:text-white'" href="#0" @click.prevent="parentLink.handleClick(); sidebarExpanded = true">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-500' : 'text-slate-400'" d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-600' : 'text-slate-600'" d="M12 3c-4.963 0-9 4.037-9 9s4.037 9 9 9 9-4.037 9-9-4.037-9-9-9z" />
|
||||
<path class="fill-current" :class="(currentRoute.fullPath === '/' || currentRoute.fullPath.includes('dashboard')) ? 'text-indigo-200' : 'text-slate-400'" d="M12 15c-1.654 0-3-1.346-3-3 0-.462.113-.894.3-1.285L6 6l4.714 3.301A2.973 2.973 0 0112 9c1.654 0 3 1.346 3 3s-1.346 3-3 3z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Dashboard</span>
|
||||
</div>
|
||||
<!-- Icon -->
|
||||
<div class="flex shrink-0 ml-2">
|
||||
<svg class="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" :class="parentLink.expanded && 'rotate-180'" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul class="pl-9 mt-1" :class="!parentLink.expanded && 'hidden'">
|
||||
<router-link to="/" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Main</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/analytics" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Analytics</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link to="/dashboard/fintech" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="mb-1 last:mb-0">
|
||||
<a class="block transition duration-150 truncate" :class="isExactActive ? 'text-indigo-500' : 'text-slate-400 hover:text-slate-200'" :href="href" @click="navigate">
|
||||
<span class="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Fintech</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</SidebarLinkGroup>
|
||||
|
||||
<!-- Users -->
|
||||
<router-link to="/users" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M14.5 7c4.695 0 8.5 3.184 8.5 7.111 0 1.597-.638 3.067-1.7 4.253V23l-4.108-2.148a10 10 0 01-2.692.37c-4.695 0-8.5-3.184-8.5-7.11C6 10.183 9.805 7 14.5 7z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M11 1C5.477 1 1 4.582 1 9c0 1.797.75 3.45 2 4.785V19l4.833-2.416C8.829 16.85 9.892 17 11 17c5.523 0 10-3.582 10-8s-4.477-8-10-8z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Users</span>
|
||||
</div>
|
||||
<!-- Badge -->
|
||||
<div class="flex flex-shrink-0 ml-2">
|
||||
<span class="inline-flex items-center justify-center h-5 text-xs font-medium text-white bg-indigo-500 px-2 rounded">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Keys -->
|
||||
<router-link to="/keys" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M14.5 7c4.695 0 8.5 3.184 8.5 7.111 0 1.597-.638 3.067-1.7 4.253V23l-4.108-2.148a10 10 0 01-2.692.37c-4.695 0-8.5-3.184-8.5-7.11C6 10.183 9.805 7 14.5 7z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M11 1C5.477 1 1 4.582 1 9c0 1.797.75 3.45 2 4.785V19l4.833-2.416C8.829 16.85 9.892 17 11 17c5.523 0 10-3.582 10-8s-4.477-8-10-8z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Keys</span>
|
||||
</div>
|
||||
<!-- Badge -->
|
||||
<div class="flex flex-shrink-0 ml-2">
|
||||
<span class="inline-flex items-center justify-center h-5 text-xs font-medium text-white bg-indigo-500 px-2 rounded">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Usages -->
|
||||
<router-link to="/usages" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M14.5 7c4.695 0 8.5 3.184 8.5 7.111 0 1.597-.638 3.067-1.7 4.253V23l-4.108-2.148a10 10 0 01-2.692.37c-4.695 0-8.5-3.184-8.5-7.11C6 10.183 9.805 7 14.5 7z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M11 1C5.477 1 1 4.582 1 9c0 1.797.75 3.45 2 4.785V19l4.833-2.416C8.829 16.85 9.892 17 11 17c5.523 0 10-3.582 10-8s-4.477-8-10-8z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Usages</span>
|
||||
</div>
|
||||
<!-- Badge -->
|
||||
<div class="flex flex-shrink-0 ml-2">
|
||||
<span class="inline-flex items-center justify-center h-5 text-xs font-medium text-white bg-indigo-500 px-2 rounded">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- About -->
|
||||
<router-link to="/about" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M16 13v4H8v-4H0l3-9h18l3 9h-8Z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="m23.72 12 .229.686A.984.984 0 0 1 24 13v8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-8c0-.107.017-.213.051-.314L.28 12H8v4h8v-4H23.72ZM13 0v7h3l-4 5-4-5h3V0h2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">About</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<!-- Login -->
|
||||
<router-link to="/signin" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-slate-900'">
|
||||
<a class="block text-slate-200 truncate transition duration-150" :class="isExactActive ? 'hover:text-slate-200' : 'hover:text-white'" :href="href" @click="navigate">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-500' : 'text-slate-600'" d="M1 3h22v20H1z" />
|
||||
<path class="fill-current" :class="isExactActive ? 'text-indigo-300' : 'text-slate-400'" d="M21 3h2v4H1V3h2V1h4v2h10V1h4v2Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Signin</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expand / collapse button -->
|
||||
<div class="pt-3 hidden lg:inline-flex 2xl:hidden justify-end mt-auto">
|
||||
<div class="px-3 py-2">
|
||||
<button @click.prevent="sidebarExpanded = !sidebarExpanded">
|
||||
<span class="sr-only">Expand / collapse sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current sidebar-expanded:rotate-180" viewBox="0 0 24 24">
|
||||
<path class="text-slate-400" d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z" />
|
||||
<path class="text-slate-600" d="M3 23H1V1h2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import SidebarLinkGroup from './SidebarLinkGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'Sidebar',
|
||||
props: ['sidebarOpen'],
|
||||
components: {
|
||||
SidebarLinkGroup,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const trigger = ref(null)
|
||||
const sidebar = ref(null)
|
||||
|
||||
const storedSidebarExpanded = localStorage.getItem('sidebar-expanded')
|
||||
const sidebarExpanded = ref(storedSidebarExpanded === null ? false : storedSidebarExpanded === 'true')
|
||||
|
||||
const currentRoute = useRouter().currentRoute.value
|
||||
|
||||
// close on click outside
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!sidebar.value || !trigger.value) return
|
||||
if (
|
||||
!props.sidebarOpen ||
|
||||
sidebar.value.contains(target) ||
|
||||
trigger.value.contains(target)
|
||||
) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
// close if the esc key is pressed
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!props.sidebarOpen || keyCode !== 27) return
|
||||
emit('close-sidebar')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickHandler)
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickHandler)
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
})
|
||||
|
||||
watch(sidebarExpanded, () => {
|
||||
localStorage.setItem('sidebar-expanded', sidebarExpanded.value)
|
||||
if (sidebarExpanded.value) {
|
||||
document.querySelector('body').classList.add('sidebar-expanded')
|
||||
} else {
|
||||
document.querySelector('body').classList.remove('sidebar-expanded')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
trigger,
|
||||
sidebar,
|
||||
sidebarExpanded,
|
||||
currentRoute,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
26
src/partials/SidebarLinkGroup copy.vue
Normal file
26
src/partials/SidebarLinkGroup copy.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="activeCondition && 'bg-slate-900'">
|
||||
<slot :handleClick="handleClick" :expanded="expanded" />
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'SidebarLinkGroup',
|
||||
props: ['activeCondition'],
|
||||
setup(props) {
|
||||
const expanded = ref(props.activeCondition)
|
||||
|
||||
const handleClick = () => {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
|
||||
return {
|
||||
expanded,
|
||||
handleClick,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
19
src/partials/SidebarLinkGroup.vue
Normal file
19
src/partials/SidebarLinkGroup.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="activeCondition && 'bg-slate-900'">
|
||||
<slot :handleClick="handleClick" :expanded="expanded" />
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = {
|
||||
activeCondition: Boolean,
|
||||
}
|
||||
|
||||
const expanded = ref(props.activeCondition)
|
||||
|
||||
const handleClick = () => {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
</script>
|
||||
26
src/router.js
Normal file
26
src/router.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
|
||||
|
||||
import Dashboard from './pages/DashBoard.vue'
|
||||
|
||||
|
||||
|
||||
// const routerHisstory = createWebHashHistory()
|
||||
const routerHashHistory = createWebHashHistory()
|
||||
|
||||
let routes = [
|
||||
{ path: '/', component: Dashboard },
|
||||
{ path: '/users',component:()=>import('./pages/Users.vue')},
|
||||
{ path: '/keys',component:()=>import('./pages/Keys.vue')},
|
||||
{ path: '/usages',component:()=>import('./pages/Usages.vue')},
|
||||
{ path: '/about', component:()=>import('./pages/About.vue')},
|
||||
{ path: '/signin',component:()=>import('./pages/Signin.vue')},
|
||||
]
|
||||
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: routerHashHistory,
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
34
src/router.json
Normal file
34
src/router.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"path": "/",
|
||||
"name": "Dashboard",
|
||||
"children": [
|
||||
{
|
||||
"path": "",
|
||||
"name": "Main"
|
||||
},
|
||||
{
|
||||
"path": "analytics",
|
||||
"name": "Analytics"
|
||||
},
|
||||
{
|
||||
"path": "fintech",
|
||||
"name": "Fintech"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/messages",
|
||||
"name": "Messages"
|
||||
},
|
||||
{
|
||||
"path": "/about",
|
||||
"name": "About"
|
||||
},
|
||||
{
|
||||
"path": "/calendar",
|
||||
"name": "Calendar"
|
||||
}
|
||||
]
|
||||
}
|
||||
25
vite.config.js
Normal file
25
vite.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'process.env': process.env
|
||||
},
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: /^~.+/,
|
||||
replacement: (val) => {
|
||||
return val.replace(/^~/, "");
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user