first commit

This commit is contained in:
pasqualevitiello
2021-11-17 18:24:56 +01:00
parent 28f44ca066
commit 0a54a0c881
69 changed files with 6127 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

39
CHANGELOG.md Normal file
View File

@@ -0,0 +1,39 @@
# CHANGELOG.md
## [3.2.0] - 2021-11-17
Fix real time chart + mc
## [3.1.0] - 2021-10-29
Fix build error
- src/utils/Utils.js
- vite.config.js
- package.json
- package-lock.json
## [3.0.0] - 2021-10-20
Add more pages
Update dependencies and remove some
## [2.2.0] - 2021-09-09
Add Cart, Cart 2 and Pay pages
## [2.1.0] - 2021-09-06
Add Invoices page
## [2.0.2] - 2021-08-30
Improve colors in settings sidebar and fix dashboard icon
## [2.0.1] - 2021-08-30
Sidebar fix
## [2.0.0] - 2021-08-30
Add more pages and components
## [1.0.0] - 2021-05-06
First release

20
index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body class="font-inter antialiased bg-gray-100 text-gray-600">
<script>
if (localStorage.getItem('sidebar-expanded') == 'true') {
document.querySelector('body').classList.add('sidebar-expanded');
} else {
document.querySelector('body').classList.remove('sidebar-expanded');
}
</script>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

1375
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "mosaic-light-vue",
"version": "0.0.0",
"scripts": {
"dev": "TAILWIND_MODE=watch vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@tailwindcss/forms": "^0.3.4",
"chart.js": "^3.5.1",
"chartjs-adapter-moment": "^1.0.0",
"cruip-js-toolkit": "^1.1.3",
"flatpickr": "^4.6.9",
"moment": "^2.29.1",
"vue": "^3.2.20",
"vue-flatpickr-component": "^9.0.5",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.9.3",
"@vue/compiler-sfc": "^3.2.20",
"autoprefixer": "^10.3.7",
"postcss": "^8.3.9",
"sass": "^1.43.2",
"tailwindcss": "^2.2.17",
"vite": "^2.5.7"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: { config: './src/css/tailwind.config.js' },
autoprefixer: {},
},
}

1
public/_redirects Normal file
View File

@@ -0,0 +1 @@
/* /index.html 200

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

21
src/App.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<router-view />
</template>
<script>
import { focusHandling } from 'cruip-js-toolkit'
import './charts/ChartjsConfig';
export default {
mounted() {
if (this.$router) {
this.$watch('$route', () => {
this.$nextTick(() => {
focusHandling('outline')
})
});
}
}
}
</script>

166
src/charts/BarChart01.vue Normal file
View File

@@ -0,0 +1,166 @@
<template>
<div class="px-5 py-3">
<ul ref="legend" class="flex flex-wrap"></ul>
</div>
<div class="flex-grow">
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { focusHandling } from 'cruip-js-toolkit'
import {
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig, formatValue } from '../utils/Utils'
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend)
export default {
name: 'BarChart01',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
const legend = ref(null)
let chart = null
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'bar',
data: props.data,
options: {
layout: {
padding: {
top: 12,
bottom: 16,
left: 20,
right: 20,
},
},
scales: {
y: {
grid: {
drawBorder: false,
},
ticks: {
maxTicksLimit: 5,
callback: (value) => formatValue(value),
},
},
x: {
type: 'time',
time: {
parser: 'MM-DD-YYYY',
unit: 'month',
displayFormats: {
month: 'MMM YY',
},
},
grid: {
display: false,
drawBorder: false,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => formatValue(context.parsed.y),
},
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: {
duration: 500,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.value
if (!ul) return
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove()
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c)
items.forEach((item) => {
const li = document.createElement('li')
li.style.marginRight = tailwindConfig().theme.margin[4]
// Button element
const button = document.createElement('button')
button.style.display = 'inline-flex'
button.style.alignItems = 'center'
button.style.opacity = item.hidden ? '.3' : ''
button.onclick = () => {
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex))
c.update()
focusHandling('outline')
}
// Color box
const box = document.createElement('span')
box.style.display = 'block'
box.style.width = tailwindConfig().theme.width[3]
box.style.height = tailwindConfig().theme.height[3]
box.style.borderRadius = tailwindConfig().theme.borderRadius.full
box.style.marginRight = tailwindConfig().theme.margin[2]
box.style.borderWidth = '3px'
box.style.borderColor = item.fillStyle
box.style.pointerEvents = 'none'
// Label
const labelContainer = document.createElement('span')
labelContainer.style.display = 'flex'
labelContainer.style.alignItems = 'center'
const value = document.createElement('span')
value.style.color = tailwindConfig().theme.colors.gray[800]
value.style.fontSize = tailwindConfig().theme.fontSize['3xl'][0]
value.style.lineHeight = tailwindConfig().theme.fontSize['3xl'][1].lineHeight
value.style.fontWeight = tailwindConfig().theme.fontWeight.bold
value.style.marginRight = tailwindConfig().theme.margin[2]
value.style.pointerEvents = 'none'
const label = document.createElement('span')
label.style.color = tailwindConfig().theme.colors.gray[500]
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0]
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight
const theValue = c.data.datasets[item.datasetIndex].data.reduce((a, b) => a + b, 0)
const valueText = document.createTextNode(formatValue(theValue))
const labelText = document.createTextNode(item.text)
value.appendChild(valueText)
label.appendChild(labelText)
li.appendChild(button)
button.appendChild(box)
button.appendChild(labelContainer)
labelContainer.appendChild(value)
labelContainer.appendChild(label)
ul.appendChild(li)
})
},
}],
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
legend,
}
}
}
</script>

102
src/charts/BarChart02.vue Normal file
View File

@@ -0,0 +1,102 @@
<template>
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import {
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { formatValue } from '../utils/Utils'
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend)
export default {
name: 'BarChart02',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
let chart = null
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'bar',
data: props.data,
options: {
layout: {
padding: {
top: 12,
bottom: 16,
left: 20,
right: 20,
},
},
scales: {
y: {
stacked: true,
grid: {
drawBorder: false,
},
beginAtZero: true,
ticks: {
maxTicksLimit: 5,
callback: (value) => formatValue(value),
},
},
x: {
stacked: true,
type: 'time',
time: {
parser: 'MM-DD-YYYY',
unit: 'month',
displayFormats: {
month: 'MMM YY',
},
},
grid: {
display: false,
drawBorder: false,
},
ticks: {
autoSkipPadding: 48,
maxRotation: 0,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => formatValue(context.parsed.y),
},
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: {
duration: 200,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
}
}
}
</script>

143
src/charts/BarChart03.vue Normal file
View File

@@ -0,0 +1,143 @@
<template>
<div class="flex-grow flex flex-col justify-center">
<div>
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</div>
<div class="px-5 pt-2 pb-2">
<ul ref="legend" class="text-sm divide-y divide-gray-100"></ul>
<ul class="text-sm divide-y divide-gray-100"></ul>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import {
Chart, BarController, BarElement, LinearScale, CategoryScale, Tooltip, Legend,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig } from '../utils/Utils'
Chart.register(BarController, BarElement, LinearScale, CategoryScale, Tooltip, Legend)
export default {
name: 'BarChart03',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
const legend = ref(null)
let chart = null
onMounted(() => {
// Calculate sum of values
const reducer = (accumulator, currentValue) => accumulator + currentValue
const values = props.data.datasets.map(x => x.data.reduce(reducer))
const max = values.reduce(reducer)
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'bar',
data: props.data,
options: {
indexAxis: 'y',
layout: {
padding: {
top: 12,
bottom: 12,
left: 20,
right: 20,
},
},
scales: {
x: {
stacked: true,
display: false,
max: max,
},
y: {
stacked: true,
display: false,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => context.parsed.x,
},
},
},
interaction: {
intersect: false,
mode: 'nearest'
},
animation: {
duration: 500,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.value
if (!ul) return
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove()
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c)
items.forEach((item) => {
const li = document.createElement('li')
li.style.display = 'flex'
li.style.justifyContent = 'space-between'
li.style.alignItems = 'center'
li.style.paddingTop = tailwindConfig().theme.padding[2.5]
li.style.paddingBottom = tailwindConfig().theme.padding[2.5]
const wrapper = document.createElement('div')
wrapper.style.display = 'flex'
wrapper.style.alignItems = 'center'
const box = document.createElement('div')
box.style.width = tailwindConfig().theme.width[3]
box.style.height = tailwindConfig().theme.width[3]
box.style.borderRadius = tailwindConfig().theme.borderRadius.sm
box.style.marginRight = tailwindConfig().theme.margin[3]
box.style.backgroundColor = item.fillStyle
const label = document.createElement('div')
const value = document.createElement('div')
value.style.fontWeight = tailwindConfig().theme.fontWeight.medium
value.style.marginLeft = tailwindConfig().theme.margin[3]
value.style.color = item.text === 'Other' ? tailwindConfig().theme.colors.gray[400] : item.fillStyle
const theValue = c.data.datasets[item.datasetIndex].data.reduce((a, b) => a + b, 0)
const valueText = document.createTextNode(`${parseInt(theValue / max * 100)}%`)
const labelText = document.createTextNode(item.text)
value.appendChild(valueText)
label.appendChild(labelText)
ul.appendChild(li)
li.appendChild(wrapper)
li.appendChild(value)
wrapper.appendChild(box)
wrapper.appendChild(label)
})
},
}],
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
legend,
}
}
}
</script>

View File

@@ -0,0 +1,42 @@
// Import Chart.js
import { Chart, Tooltip } from 'chart.js'
// Import Tailwind config
import { tailwindConfig } from '../utils/Utils'
Chart.register(Tooltip)
// Define Chart.js default settings
Chart.defaults.font.family = '"Inter", sans-serif'
Chart.defaults.font.weight = '500'
Chart.defaults.color = tailwindConfig().theme.colors.gray[400]
Chart.defaults.scale.grid.color = tailwindConfig().theme.colors.gray[100]
Chart.defaults.plugins.tooltip.titleColor = tailwindConfig().theme.colors.gray[800]
Chart.defaults.plugins.tooltip.bodyColor = tailwindConfig().theme.colors.gray[800]
Chart.defaults.plugins.tooltip.backgroundColor = tailwindConfig().theme.colors.white
Chart.defaults.plugins.tooltip.borderWidth = 1
Chart.defaults.plugins.tooltip.borderColor = tailwindConfig().theme.colors.gray[200]
Chart.defaults.plugins.tooltip.displayColors = false
Chart.defaults.plugins.tooltip.mode = 'nearest'
Chart.defaults.plugins.tooltip.intersect = false
Chart.defaults.plugins.tooltip.position = 'nearest'
Chart.defaults.plugins.tooltip.caretSize = 0
Chart.defaults.plugins.tooltip.caretPadding = 20
Chart.defaults.plugins.tooltip.cornerRadius = 4
Chart.defaults.plugins.tooltip.padding = 8
// Register Chart.js plugin to add a bg option for chart area
Chart.register({
id: 'chartAreaPlugin',
// eslint-disable-next-line object-shorthand
beforeDraw: (chart) => {
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
const ctx = chart.canvas.getContext('2d')
const { chartArea } = chart
ctx.save()
ctx.fillStyle = chart.config.options.chartArea.backgroundColor
// eslint-disable-next-line max-len
ctx.fillRect(chartArea.left, chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top)
ctx.restore()
}
},
})

View File

@@ -0,0 +1,120 @@
<template>
<div class="flex-grow flex flex-col justify-center">
<div>
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</div>
<div class="px-5 pt-2 pb-6">
<ul ref="legend" class="flex flex-wrap justify-center -m-1"></ul>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { focusHandling } from 'cruip-js-toolkit'
import {
Chart, DoughnutController, ArcElement, TimeScale, Tooltip,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig } from '../utils/Utils'
Chart.register(DoughnutController, ArcElement, TimeScale, Tooltip)
export default {
name: 'DoughnutChart',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
const legend = ref(null)
let chart = null
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'doughnut',
data: props.data,
options: {
cutout: '80%',
layout: {
padding: 24,
},
plugins: {
legend: {
display: false,
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: {
duration: 500,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.value
if (!ul) return
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove()
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c)
items.forEach((item) => {
const li = document.createElement('li')
li.style.margin = tailwindConfig().theme.margin[1]
// Button element
const button = document.createElement('button')
button.classList.add('btn-xs')
button.style.backgroundColor = tailwindConfig().theme.colors.white
button.style.borderWidth = tailwindConfig().theme.borderWidth[1]
button.style.borderColor = tailwindConfig().theme.colors.gray[200]
button.style.color = tailwindConfig().theme.colors.gray[500]
button.style.boxShadow = tailwindConfig().theme.boxShadow.md
button.style.opacity = item.hidden ? '.3' : ''
button.onclick = () => {
c.toggleDataVisibility(item.index, !item.index)
c.update()
focusHandling('outline')
}
// Color box
const box = document.createElement('span')
box.style.display = 'block'
box.style.width = tailwindConfig().theme.width[2]
box.style.height = tailwindConfig().theme.height[2]
box.style.backgroundColor = item.fillStyle
box.style.borderRadius = tailwindConfig().theme.borderRadius.sm
box.style.marginRight = tailwindConfig().theme.margin[1]
box.style.pointerEvents = 'none'
// Label
const label = document.createElement('span')
label.style.display = 'flex'
label.style.alignItems = 'center'
const labelText = document.createTextNode(item.text)
label.appendChild(labelText)
li.appendChild(button)
button.appendChild(box)
button.appendChild(label)
ul.appendChild(li)
})
},
}],
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
legend,
}
}
}
</script>

View File

@@ -0,0 +1,79 @@
<template>
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import {
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig, formatValue } from '../utils/Utils'
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip)
export default {
name: 'LineChart01',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
let chart = null
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'line',
data: props.data,
options: {
chartArea: {
backgroundColor: tailwindConfig().theme.colors.gray[50],
},
layout: {
padding: 20,
},
scales: {
y: {
display: false,
beginAtZero: true,
},
x: {
type: 'time',
time: {
parser: 'MM-DD-YYYY',
unit: 'month',
},
display: false,
},
},
plugins: {
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => formatValue(context.parsed.y),
},
},
legend: {
display: false,
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
maintainAspectRatio: false,
resizeDelay: 200,
},
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
}
}
}
</script>

157
src/charts/LineChart02.vue Normal file
View File

@@ -0,0 +1,157 @@
<template>
<div class="px-5 py-3">
<div class="flex flex-wrap justify-between items-end">
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">$1,482</div>
<div class="text-sm font-semibold text-white px-1.5 bg-yellow-500 rounded-full">-22%</div>
</div>
<div class="flex-grow ml-2 mb-1">
<ul ref="legend" class="flex flex-wrap justify-end"></ul>
</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { focusHandling } from 'cruip-js-toolkit'
import {
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig, formatValue } from '../utils/Utils'
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip)
export default {
name: 'LineChart02',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
const legend = ref(null)
let chart = null
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'line',
data: props.data,
options: {
layout: {
padding: 20,
},
scales: {
y: {
grid: {
drawBorder: false,
beginAtZero: true,
},
ticks: {
maxTicksLimit: 5,
callback: (value) => formatValue(value),
},
},
x: {
type: 'time',
time: {
parser: 'MM-DD-YYYY',
unit: 'month',
displayFormats: {
month: 'MMM YY',
},
},
grid: {
display: false,
drawBorder: false,
},
ticks: {
autoSkipPadding: 48,
maxRotation: 0,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => formatValue(context.parsed.y),
},
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.value
if (!ul) return
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove()
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c)
items.slice(0, 2).forEach((item) => {
const li = document.createElement('li')
li.style.marginLeft = tailwindConfig().theme.margin[3]
// Button element
const button = document.createElement('button')
button.style.display = 'inline-flex'
button.style.alignItems = 'center'
button.style.opacity = item.hidden ? '.3' : ''
button.onclick = () => {
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex))
c.update()
focusHandling('outline')
}
// Color box
const box = document.createElement('span')
box.style.display = 'block'
box.style.width = tailwindConfig().theme.width[3]
box.style.height = tailwindConfig().theme.height[3]
box.style.borderRadius = tailwindConfig().theme.borderRadius.full
box.style.marginRight = tailwindConfig().theme.margin[2]
box.style.borderWidth = '3px'
box.style.borderColor = c.data.datasets[item.datasetIndex].borderColor
box.style.pointerEvents = 'none'
// Label
const label = document.createElement('span')
label.style.color = tailwindConfig().theme.colors.gray[500]
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0]
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight
const labelText = document.createTextNode(item.text)
label.appendChild(labelText)
li.appendChild(button)
button.appendChild(box)
button.appendChild(label)
ul.appendChild(li)
})
},
}],
})
})
onUnmounted(() => chart.destroy())
return {
canvas,
legend,
}
}
}
</script>

View File

@@ -0,0 +1,134 @@
<template>
<div class="px-5 py-3">
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2 tabular-nums">$<span ref="chartValue">57.81</span></div>
<div ref="chartDeviation" class="text-sm font-semibold text-white px-1.5 rounded-full"></div>
</div>
</div>
<div class="flex-grow">
<canvas ref="canvas" :data="data" :width="width" :height="height"></canvas>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import {
Chart, LineController, LineElement, PointElement, LinearScale, TimeScale, Tooltip,
} from 'chart.js'
import 'chartjs-adapter-moment'
// Import utilities
import { tailwindConfig, formatValue } from '../utils/Utils'
Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Tooltip)
export default {
name: 'RealtimeChart',
props: ['data', 'width', 'height'],
setup(props) {
const canvas = ref(null)
const chartValue = ref(null)
const chartDeviation = ref(null)
let chart = null
// function that updates header values
const handleHeaderValues = (data, chartValue, chartDeviation) => {
const currentValue = data.datasets[0].data[data.datasets[0].data.length - 1]
const previousValue = data.datasets[0].data[data.datasets[0].data.length - 2]
const diff = ((currentValue - previousValue) / previousValue) * 100
chartValue.value.innerHTML = data.datasets[0].data[data.datasets[0].data.length - 1]
if (diff < 0) {
chartDeviation.value.style.backgroundColor = tailwindConfig().theme.colors.yellow[500]
} else {
chartDeviation.value.style.backgroundColor = tailwindConfig().theme.colors.green[500]
}
chartDeviation.value.innerHTML = `${diff > 0 ? '+' : ''}${diff.toFixed(2)}%`
}
onMounted(() => {
const ctx = canvas.value
chart = new Chart(ctx, {
type: 'line',
data: props.data,
options: {
layout: {
padding: 20,
},
scales: {
y: {
grid: {
drawBorder: false,
},
suggestedMin: 30,
suggestedMax: 80,
ticks: {
maxTicksLimit: 5,
callback: (value) => formatValue(value),
},
},
x: {
type: 'time',
time: {
parser: 'hh:mm:ss',
unit: 'second',
tooltipFormat: 'MMM DD, H:mm:ss a',
displayFormats: {
second: 'H:mm:ss',
},
},
grid: {
display: false,
drawBorder: false,
},
ticks: {
autoSkipPadding: 48,
maxRotation: 0,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
titleFont: {
weight: '600',
},
callbacks: {
label: (context) => formatValue(context.parsed.y),
},
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: false,
maintainAspectRatio: false,
resizeDelay: 200,
},
})
// output header values
handleHeaderValues(props.data, chartValue, chartDeviation)
})
onUnmounted(() => chart.destroy())
watch(
() => props.data,
(data) => {
// update chart
chart.data = data
chart.update()
}
)
return {
canvas,
chartValue,
chartDeviation,
}
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div class="relative">
<flat-pickr class="form-input pl-9 text-gray-500 hover:text-gray-600 font-medium focus:border-gray-300 w-60" :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-gray-500 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>

View File

@@ -0,0 +1,80 @@
<template>
<div>
<button
ref="trigger"
class="text-gray-400 hover:text-gray-500 rounded-full"
:class="{ 'bg-gray-100 text-gray-500': dropdownOpen }"
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 border border-gray-200 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>

View File

@@ -0,0 +1,121 @@
<template>
<div class="relative inline-flex">
<button
ref="trigger"
class="btn bg-white border-gray-200 hover:border-gray-300 text-gray-500 hover:text-gray-600"
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 min-w-56 bg-white border border-gray-200 pt-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
<div ref="dropdown">
<div class="text-xs font-semibold text-gray-400 uppercase pt-1.5 pb-2 px-4">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-gray-200 bg-gray-50">
<ul class="flex items-center justify-between">
<li>
<button class="btn-xs bg-white border-gray-200 hover:border-gray-300 text-gray-500 hover:text-gray-600">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>

View 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-gray-100 hover:bg-gray-200 transition duration-150 rounded-full"
:class="{ 'bg-gray-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-gray-500" 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 border border-gray-200 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
<div class="text-xs font-semibold text-gray-400 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 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
<svg class="w-3 h-3 fill-current text-indigo-300 flex-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 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
<svg class="w-3 h-3 fill-current text-indigo-300 flex-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 flex items-center py-1 px-3" to="#0" @click="dropdownOpen = false">
<svg class="w-3 h-3 fill-current text-indigo-300 flex-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>

View 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-gray-100 hover:bg-gray-200 transition duration-150 rounded-full"
:class="{ 'bg-gray-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-gray-500" 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-gray-400" 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-red-500 border-2 border-white 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 border border-gray-200 py-1.5 rounded shadow-lg overflow-hidden mt-1" :class="align === 'right' ? 'right-0' : 'left-0'">
<div class="text-xs font-semibold text-gray-400 uppercase pt-1.5 pb-2 px-4">Notifications</div>
<ul
ref="dropdown"
@focusin="dropdownOpen = true"
@focusout="dropdownOpen = false"
>
<li class="border-b border-gray-200 last:border-0">
<router-link class="block py-2 px-4 hover:bg-gray-50" to="#0" @click="dropdownOpen = false">
<span class="block text-sm mb-2">📣 <span class="font-medium text-gray-800">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-gray-400">Feb 12, 2021</span>
</router-link>
</li>
<li class="border-b border-gray-200 last:border-0">
<router-link class="block py-2 px-4 hover:bg-gray-50" to="#0" @click="dropdownOpen = false">
<span class="block text-sm mb-2">📣 <span class="font-medium text-gray-800">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-gray-400">Feb 9, 2021</span>
</router-link>
</li>
<li class="border-b border-gray-200 last:border-0">
<router-link class="block py-2 px-4 hover:bg-gray-50" to="#0" @click="dropdownOpen = false">
<span class="block text-sm mb-2">🚀<span class="font-medium text-gray-800">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-gray-400">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>

View 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 group-hover:text-gray-800">Acme Inc.</span>
<svg class="w-3 h-3 flex-shrink-0 ml-1 fill-current text-gray-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 border border-gray-200 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-gray-200">
<div class="font-medium text-gray-800">Acme Inc.</div>
<div class="text-xs text-gray-500 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 flex items-center py-1 px-3" to="/" @click="dropdownOpen = false">Settings</router-link>
</li>
<li>
<router-link class="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3" to="/" @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>

View 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-gray-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 transform px-4 sm:px-6" role="dialog" aria-modal="true">
<div ref="modalContent" class="bg-white overflow-auto max-w-2xl w-full max-h-full rounded shadow-lg">
<!-- Search form -->
<form class="border-b border-gray-200">
<div class="relative">
<label :for="searchId" class="sr-only">Search</label>
<input :id="searchId" class="w-full border-0 focus:ring-transparent placeholder-gray-400 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 flex-shrink-0 fill-current text-gray-400 group-hover:text-gray-500 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-gray-400 uppercase px-2 mb-2">Recent searches</div>
<ul class="text-sm">
<li>
<router-link class="flex items-center p-2 text-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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-gray-400 uppercase px-2 mb-2">Recent pages</div>
<ul class="text-sm">
<li>
<router-link class="flex items-center p-2 text-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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 text-gray-800 group-hover:text-white">Messages</span> - Conversation / / Mike Mills</span>
</router-link>
</li>
<li>
<router-link class="flex items-center p-2 text-gray-800 hover:text-white hover:bg-indigo-500 rounded group" to="#0" @click="$emit('close-modal')">
<svg class="w-4 h-4 fill-current text-gray-400 group-hover:text-white group-hover:text-opacity-50 flex-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 text-gray-800 group-hover:text-white">Messages</span> - Conversation / / Eva Patrick</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>

100
src/components/Tooltip.vue Normal file
View File

@@ -0,0 +1,100 @@
<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-gray-400" 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 overflow-hidden"
:class="[
bg === 'dark' ? 'bg-gray-800' : 'bg-white border border-gray-200 shadow-lg',
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 transform -translate-y-1/2';
case 'left':
return 'right-full top-1/2 transform -translate-y-1/2';
case 'bottom':
return 'top-full left-1/2 transform -translate-x-1/2';
default:
return 'bottom-full left-1/2 transform -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 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,
positionInnerClasses,
}
}
}
</script>

View File

@@ -0,0 +1,228 @@
// Import base styles
@import '~flatpickr/dist/flatpickr.min.css';
// Customise flatpickr
$calendarPadding: 24px;
$daySize: 36px;
$daysWidth: $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 rounded shadow-lg border border-gray-200 left-1/2;
margin-left: - ($daysWidth + $calendarPadding*2)*0.5;
padding: $calendarPadding;
width: $daysWidth + $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: $daysWidth;
}
.dayContainer {
width: $daysWidth;
min-width: $daysWidth;
max-width: $daysWidth;
}
.flatpickr-day {
@apply bg-gray-50 text-sm font-medium text-gray-600;
max-width: $daySize;
height: $daySize;
line-height: $daySize;
}
.flatpickr-day,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
border: none;
}
.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-gray-400;
}
.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-gray-600;
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
width: 7px;
height: 11px;
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover,
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: inherit;
@apply text-gray-400;
}
.flatpickr-months .flatpickr-prev-month {
margin-left: -10px;
}
.flatpickr-months .flatpickr-next-month {
margin-right: -10px;
}
.flatpickr-months .flatpickr-month {
@apply text-gray-800;
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-gray-400 font-medium text-xs;
}
.flatpickr-calendar.arrowTop::before,
.flatpickr-calendar.arrowTop::after {
display: none;
}

View File

@@ -0,0 +1,55 @@
// Range slider
$range-thumb-size: 36px;
input[type=range] {
appearance: none;
background: #ccc;
border-radius: 3px;
height: 6px;
margin-top: ($range-thumb-size - 6px) * 0.5;
margin-bottom: ($range-thumb-size - 6px) * 0.5;
--thumb-size: #{$range-thumb-size};
&::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
background-color: #000;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
border: 0;
border-radius: 50%;
cursor: pointer;
height: $range-thumb-size;
width: $range-thumb-size;
}
&::-moz-range-thumb {
background-color: #000;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
border: 0;
border: none;
border-radius: 50%;
cursor: pointer;
height: $range-thumb-size;
width: $range-thumb-size;
}
&::-ms-thumb {
background-color: #000;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
border: 0;
border-radius: 50%;
cursor: pointer;
height: $range-thumb-size;
width: $range-thumb-size;
}
&::-moz-focus-outer {
border: 0;
}
}

View File

@@ -0,0 +1,24 @@
:focus,
button:focus,
.btn:focus,
.btn-sm:focus,
.form-input:focus,
.form-textarea:focus,
.form-multiselect:focus,
.form-select:focus,
.form-checkbox:focus,
.form-radio:focus {
@apply outline-blue;
}
.form-input,
.form-textarea,
.form-multiselect,
.form-select,
.form-checkbox,
.form-radio {
&:focus {
box-shadow: none !important;
}
}

View File

@@ -0,0 +1,44 @@
// Switch element
.form-switch {
@apply relative select-none;
width: 44px;
label {
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
> span:first-child {
@apply absolute block rounded-full;
width: 20px;
height: 20px;
top: 2px;
left: 2px;
right: 50%;
transition: all .15s ease-out;
}
}
input[type="checkbox"] {
&:checked {
+ label {
@apply bg-indigo-500;
> span:first-child {
left: 22px;
}
}
}
&:disabled {
+ label {
@apply cursor-not-allowed bg-gray-100 border border-gray-200;
> span:first-child {
@apply bg-gray-400;
}
}
}
}
}

View File

@@ -0,0 +1,106 @@
// 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 text-sm text-gray-800 bg-white border;
}
.form-input,
.form-textarea,
.form-multiselect,
.form-select,
.form-checkbox {
@apply rounded;
}
.form-input,
.form-textarea,
.form-multiselect,
.form-select {
@apply leading-5 py-2 px-3 border-gray-200 hover:border-gray-300 focus:border-indigo-300 shadow-sm;
}
.form-input,
.form-textarea {
@apply placeholder-gray-400;
}
.form-select {
@apply pr-10;
}
.form-checkbox,
.form-radio {
@apply text-indigo-500 border border-gray-300;
}
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

13
src/css/style.scss Normal file
View File

@@ -0,0 +1,13 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback');
@tailwind base;
@tailwind components;
// Additional styles
@import 'additional-styles/utility-patterns.scss';
@import 'additional-styles/range-slider.scss';
@import 'additional-styles/toggle-switch.scss';
@import 'additional-styles/flatpickr.scss';
@import 'additional-styles/theme.scss';
@tailwind utilities;

View File

@@ -0,0 +1,71 @@
const colors = require('tailwindcss/colors');
const plugin = require('tailwindcss/plugin');
module.exports = {
mode: 'jit',
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
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)',
},
colors: {
gray: colors.blueGray,
'light-blue': colors.sky,
red: colors.rose,
},
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}`)}`);
});
}),
],
};

20
src/images/icon-01.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="icon1-b">
<stop stop-color="#A5B4FC" offset="0%" />
<stop stop-color="#818CF8" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="24.537%" x2="50%" y2="100%" id="icon1-c">
<stop stop-color="#4338CA" offset="0%" />
<stop stop-color="#6366F1" stop-opacity="0" offset="100%" />
</linearGradient>
<path id="icon1-a" d="M16 0l16 32-16-5-16 5z" />
</defs>
<g transform="rotate(90 16 16)" fill="none" fill-rule="evenodd">
<mask id="icon1-d" fill="#fff">
<use xlink:href="#icon1-a" />
</mask>
<use fill="url(#icon1-b)" xlink:href="#icon1-a" />
<path fill="url(#icon1-c)" mask="url(#icon1-d)" d="M16-6h20v38H16z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 969 B

20
src/images/icon-02.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="icon2-b">
<stop stop-color="#BAE6FD" offset="0%" />
<stop stop-color="#38BDF8" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="25.718%" x2="50%" y2="100%" id="icon2-c">
<stop stop-color="#0284C7" offset="0%" />
<stop stop-color="#0284C7" stop-opacity="0" offset="100%" />
</linearGradient>
<path id="icon2-a" d="M16 0l16 32-16-5-16 5z" />
</defs>
<g transform="rotate(90 16 16)" fill="none" fill-rule="evenodd">
<mask id="icon2-d" fill="#fff">
<use xlink:href="#icon2-a" />
</mask>
<use fill="url(#icon2-b)" xlink:href="#icon2-a" />
<path fill="url(#icon2-c)" mask="url(#icon2-d)" d="M16-6h20v38H16z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 969 B

20
src/images/icon-03.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="icon3-b">
<stop stop-color="#E2E8F0" offset="0%" />
<stop stop-color="#94A3B8" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="24.537%" x2="50%" y2="99.142%" id="icon3-c">
<stop stop-color="#334155" offset="0%" />
<stop stop-color="#334155" stop-opacity="0" offset="100%" />
</linearGradient>
<path id="icon3-a" d="M16 0l16 32-16-5-16 5z" />
</defs>
<g transform="rotate(90 16 16)" fill="none" fill-rule="evenodd">
<mask id="icon3-d" fill="#fff">
<use xlink:href="#icon3-a" />
</mask>
<use fill="url(#icon3-b)" xlink:href="#icon3-a" />
<path fill="url(#icon3-c)" mask="url(#icon3-d)" d="M16-6h20v38H16z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 972 B

BIN
src/images/user-36-01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/user-36-02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/user-36-03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

BIN
src/images/user-36-04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/images/user-36-05.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/images/user-36-06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/images/user-36-07.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/images/user-36-08.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/images/user-36-09.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

9
src/main.js Normal file
View File

@@ -0,0 +1,9 @@
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import './css/style.scss'
const app = createApp(App)
app.use(router)
app.mount('#app')

141
src/pages/Dashboard.vue Normal file
View File

@@ -0,0 +1,141 @@
<template>
<div class="flex h-screen 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>
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
<!-- Welcome banner -->
<WelcomeBanner />
<!-- Dashboard actions -->
<div class="sm:flex sm:justify-between sm:items-center mb-8">
<!-- Left: Avatars -->
<DashboardAvatars />
<!-- Right: Actions -->
<div class="grid grid-flow-col sm:auto-cols-max justify-start sm:justify-end gap-2">
<!-- Filter button -->
<FilterButton align="right" />
<!-- Datepicker built with flatpickr -->
<Datepicker align="right" />
<!-- Add view button -->
<button class="btn bg-indigo-500 hover:bg-indigo-600 text-white">
<svg class="w-4 h-4 fill-current opacity-50 flex-shrink-0" viewBox="0 0 16 16">
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
</svg>
<span class="hidden xs:block ml-2">Add view</span>
</button>
</div>
</div>
<!-- Cards -->
<div class="grid grid-cols-12 gap-6">
<!-- Line chart (Acme Plus) -->
<DashboardCard01 />
<!-- Line chart (Acme Advanced) -->
<DashboardCard02 />
<!-- Line chart (Acme Professional) -->
<DashboardCard03 />
<!-- Bar chart (Direct vs Indirect) -->
<DashboardCard04 />
<!-- Line chart (Real Time Value) -->
<DashboardCard05 />
<!-- Doughnut chart (Top Countries) -->
<DashboardCard06 />
<!-- Table (Top Channels) -->
<DashboardCard07 />
<!-- Line chart (Sales Over Time) -->
<DashboardCard08 />
<!-- Stacked bar chart (Sales VS Refunds) -->
<DashboardCard09 />
<!-- Card (Customers) -->
<DashboardCard10 />
<!-- Card (Reasons for Refunds) -->
<DashboardCard11 />
<!-- Card (Recent Activity) -->
<DashboardCard12 />
<!-- Card (Income/Expenses) -->
<DashboardCard13 />
</div>
</div>
</main>
<Banner />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import Sidebar from '../partials/Sidebar.vue'
import Header from '../partials/Header.vue'
import WelcomeBanner from '../partials/dashboard/WelcomeBanner.vue'
import DashboardAvatars from '../partials/dashboard/DashboardAvatars.vue'
import FilterButton from '../components/DropdownFilter.vue'
import Datepicker from '../components/Datepicker.vue'
import DashboardCard01 from '../partials/dashboard/DashboardCard01.vue'
import DashboardCard02 from '../partials/dashboard/DashboardCard02.vue'
import DashboardCard03 from '../partials/dashboard/DashboardCard03.vue'
import DashboardCard04 from '../partials/dashboard/DashboardCard04.vue'
import DashboardCard05 from '../partials/dashboard/DashboardCard05.vue'
import DashboardCard06 from '../partials/dashboard/DashboardCard06.vue'
import DashboardCard07 from '../partials/dashboard/DashboardCard07.vue'
import DashboardCard08 from '../partials/dashboard/DashboardCard08.vue'
import DashboardCard09 from '../partials/dashboard/DashboardCard09.vue'
import DashboardCard10 from '../partials/dashboard/DashboardCard10.vue'
import DashboardCard11 from '../partials/dashboard/DashboardCard11.vue'
import DashboardCard12 from '../partials/dashboard/DashboardCard12.vue'
import DashboardCard13 from '../partials/dashboard/DashboardCard13.vue'
import Banner from '../partials/Banner.vue'
export default {
name: 'Dashboard',
components: {
Sidebar,
Header,
WelcomeBanner,
DashboardAvatars,
FilterButton,
Datepicker,
DashboardCard01,
DashboardCard02,
DashboardCard03,
DashboardCard04,
DashboardCard05,
DashboardCard06,
DashboardCard07,
DashboardCard08,
DashboardCard09,
DashboardCard10,
DashboardCard11,
DashboardCard12,
DashboardCard13,
Banner,
},
setup() {
const sidebarOpen = ref(false)
return {
sidebarOpen,
}
}
}
</script>

28
src/partials/Banner.vue Normal file
View File

@@ -0,0 +1,28 @@
<template>
<div v-show="open" class="fixed bottom-0 right-0 w-full md:bottom-8 md:right-12 md:w-auto z-60">
<div class="bg-gray-800 text-gray-50 text-sm p-3 md:rounded shadow-lg flex justify-between">
<div>👉 <a class="hover:underline" href="https://github.com/cruip/vuejs-admin-dashboard-template" target="_blank" rel="noreferrer">Download Mosaic Lite on GitHub</a></div>
<button class="text-gray-500 hover:text-gray-400 ml-5" @click="open = false">
<span class="sr-only">Close</span>
<svg class="w-4 h-4 flex-shrink-0 fill-current" viewBox="0 0 16 16">
<path d="M12.72 3.293a1 1 0 00-1.415 0L8.012 6.586 4.72 3.293a1 1 0 00-1.414 1.414L6.598 8l-3.293 3.293a1 1 0 101.414 1.414l3.293-3.293 3.293 3.293a1 1 0 001.414-1.414L9.426 8l3.293-3.293a1 1 0 000-1.414z" />
</svg>
</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Banner',
props: ['open'],
setup() {
const open = ref(true)
return {
open,
}
}
}
</script>

74
src/partials/Header.vue Normal file
View File

@@ -0,0 +1,74 @@
<template>
<header class="sticky top-0 bg-white border-b border-gray-200 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-gray-500 hover:text-gray-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">
<button
class="w-8 h-8 flex items-center justify-center bg-gray-100 hover:bg-gray-200 transition duration-150 rounded-full ml-3"
:class="{ 'bg-gray-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-gray-500" 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-gray-400" 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" />
<Notifications align="right" />
<Help align="right" />
<!-- Divider -->
<hr class="w-px h-6 bg-gray-200" />
<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 UserMenu from '../components/DropdownProfile.vue'
export default {
name: 'Header',
props: ['sidebarOpen'],
components: {
SearchModal,
Notifications,
Help,
UserMenu,
},
setup() {
const searchModalOpen = ref(false)
return {
searchModalOpen,
}
}
}
</script>

508
src/partials/Sidebar.vue Normal file
View File

@@ -0,0 +1,508 @@
<template>
<div>
<!-- Sidebar backdrop (mobile only) -->
<div class="fixed inset-0 bg-gray-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 transform h-screen overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 flex-shrink-0 bg-gray-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-gray-500 hover:text-gray-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-gray-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 -->
<router-link to="/" custom v-slot="{ href, navigate, isExactActive }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="isExactActive && 'bg-gray-900'">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :class="isExactActive && 'hover:text-gray-200'" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-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 text-gray-600" :class="isExactActive && 'text-indigo-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 text-gray-400" :class="isExactActive && 'text-indigo-200'" 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>
</a>
</li>
</router-link>
<!-- Analytics -->
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" d="M0 20h24v2H0z" />
<path class="fill-current text-gray-400" d="M4 18h2a1 1 0 001-1V8a1 1 0 00-1-1H4a1 1 0 00-1 1v9a1 1 0 001 1zM11 18h2a1 1 0 001-1V3a1 1 0 00-1-1h-2a1 1 0 00-1 1v14a1 1 0 001 1zM17 12v5a1 1 0 001 1h2a1 1 0 001-1v-5a1 1 0 00-1-1h-2a1 1 0 00-1 1z" />
</svg>
<span class="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Analytics</span>
</div>
</a>
</li>
</router-link>
<!-- E-Commerce -->
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('ecommerce')">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :class="currentRoute.fullPath.includes('ecommerce') && 'hover:text-gray-200'" href="#0" @click.prevent="sidebarExpanded ? parentLink.handleClick() : sidebarExpanded = true">
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-400" :class="currentRoute.fullPath.includes('ecommerce') && 'text-indigo-300'" d="M13 15l11-7L11.504.136a1 1 0 00-1.019.007L0 7l13 8z" />
<path class="fill-current text-gray-700" :class="currentRoute.fullPath.includes('ecommerce') && '!text-indigo-600'" d="M13 15L0 7v9c0 .355.189.685.496.864L13 24v-9z" />
<path class="fill-current text-gray-600" :class="currentRoute.fullPath.includes('ecommerce') && 'text-indigo-500'" 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 flex-shrink-0 ml-2">
<svg class="w-3 h-3 flex-shrink-0 ml-1 fill-current text-gray-400" :class="parentLink.expanded && 'transform 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 }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Customers</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Orders</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Invoices</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Shop</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Shop 2</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Single Product</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Cart</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Cart 2</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Pay</span>
</a>
</li>
</router-link>
</ul>
</div>
</SidebarLinkGroup>
<!-- Campaigns -->
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-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 text-gray-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>
<!-- Team -->
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('team')">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :class="currentRoute.fullPath.includes('team') && 'hover:text-gray-200'" href="#0" @click.prevent="sidebarExpanded ? parentLink.handleClick() : sidebarExpanded = true">
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" :class="currentRoute.fullPath.includes('team') && 'text-indigo-500'" 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 text-gray-400" :class="currentRoute.fullPath.includes('team') && 'text-indigo-300'" 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">Team</span>
</div>
<!-- Icon -->
<div class="flex flex-shrink-0 ml-2">
<svg class="w-3 h-3 flex-shrink-0 ml-1 fill-current text-gray-400" :class="parentLink.expanded && 'transform 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 }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Team - Tabs</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Team - Tiles</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Profile</span>
</a>
</li>
</router-link>
</ul>
</div>
</SidebarLinkGroup>
<!-- Messages -->
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-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 text-gray-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>
</a>
</li>
</router-link>
<!-- Tasks -->
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" d="M8 1v2H3v19h18V3h-5V1h7v23H1V1z" />
<path class="fill-current text-gray-600" d="M1 1h22v23H1z" />
<path class="fill-current text-gray-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>
</a>
</li>
</router-link>
<!-- Inbox -->
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" d="M16 13v4H8v-4H0l3-9h18l3 9h-8Z" />
<path class="fill-current text-gray-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="/" custom v-slot="{ href, navigate }">
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :href="href" @click="navigate">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" d="M1 3h22v20H1z" />
<path class="fill-current text-gray-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>
<!-- Settings -->
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('settings')">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :class="currentRoute.fullPath.includes('settings') && 'hover:text-gray-200'" href="#0" @click.prevent="sidebarExpanded ? parentLink.handleClick() : sidebarExpanded = true">
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<path class="fill-current text-gray-600" :class="currentRoute.fullPath.includes('settings') && 'text-indigo-500'" 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 text-gray-400" :class="currentRoute.fullPath.includes('settings') && 'text-indigo-300'" 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 text-gray-600" :class="currentRoute.fullPath.includes('settings') && 'text-indigo-500'" 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 text-gray-400" :class="currentRoute.fullPath.includes('settings') && 'text-indigo-300'" 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 flex-shrink-0 ml-2">
<svg class="w-3 h-3 flex-shrink-0 ml-1 fill-current text-gray-400" :class="parentLink.expanded && 'transform 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 }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">My Account</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">My Notifications</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Connected Apps</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Plans</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Billing & Invoices</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Give Feedback</span>
</a>
</li>
</router-link>
</ul>
</div>
</SidebarLinkGroup>
<!-- Utility -->
<SidebarLinkGroup v-slot="parentLink" :activeCondition="currentRoute.fullPath.includes('utility')">
<a class="block text-gray-200 hover:text-white truncate transition duration-150" :class="currentRoute.fullPath.includes('utility') && 'hover:text-gray-200'" href="#0" @click.prevent="sidebarExpanded ? parentLink.handleClick() : sidebarExpanded = true">
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="flex-shrink-0 h-6 w-6" viewBox="0 0 24 24">
<circle class="fill-current text-gray-400" :class="currentRoute.fullPath.includes('utility') && 'text-indigo-300'" cx="18.5" cy="5.5" r="4.5" />
<circle class="fill-current text-gray-600" :class="currentRoute.fullPath.includes('utility') && 'text-indigo-500'" cx="5.5" cy="5.5" r="4.5" />
<circle class="fill-current text-gray-600" :class="currentRoute.fullPath.includes('utility') && 'text-indigo-500'" cx="18.5" cy="18.5" r="4.5" />
<circle class="fill-current text-gray-400" :class="currentRoute.fullPath.includes('utility') && 'text-indigo-300'" 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 flex-shrink-0 ml-2">
<svg class="w-3 h-3 flex-shrink-0 ml-1 fill-current text-gray-400" :class="parentLink.expanded && 'transform 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 }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Changelog</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Roadmap</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">FAQs</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">Empty State</span>
</a>
</li>
</router-link>
<router-link to="/" custom v-slot="{ href, navigate }">
<li class="mb-1 last:mb-0">
<a class="block text-gray-400 hover:text-gray-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">404</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-gray-400" d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z" />
<path class="text-gray-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>

View File

@@ -0,0 +1,26 @@
<template>
<li class="px-3 py-2 rounded-sm mb-0.5 last:mb-0" :class="activeCondition && 'bg-gray-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>

View File

@@ -0,0 +1,38 @@
<template>
<ul class="flex flex-wrap justify-center sm:justify-start mb-8 sm:mb-0 -space-x-3 -ml-px">
<li>
<router-link class="block" to="#0">
<img class="w-9 h-9 rounded-full" src="../../images/user-36-01.jpg" width="36" height="36" alt="User 01" />
</router-link>
</li>
<li>
<router-link class="block" to="#0">
<img class="w-9 h-9 rounded-full" src="../../images/user-36-02.jpg" width="36" height="36" alt="User 02" />
</router-link>
</li>
<li>
<router-link class="block" to="#0">
<img class="w-9 h-9 rounded-full" src="../../images/user-36-03.jpg" width="36" height="36" alt="User 03" />
</router-link>
</li>
<li>
<router-link class="block" to="#0">
<img class="w-9 h-9 rounded-full" src="../../images/user-36-04.jpg" width="36" height="36" alt="User 04" />
</router-link>
</li>
<li>
<button class="flex justify-center items-center w-9 h-9 rounded-full bg-white border border-gray-200 hover:border-gray-300 text-indigo-500 shadow-sm transition duration-150 ml-2">
<span class="sr-only">Add new user</span>
<svg class="w-4 h-4 fill-current" viewBox="0 0 16 16">
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
</svg>
</button>
</li>
</ul>
</template>
<script>
export default {
name: 'DashboardAvatars',
}
</script>

View File

@@ -0,0 +1,104 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 xl:col-span-4 bg-white shadow-lg rounded-sm border border-gray-200">
<div class="px-5 pt-5">
<header class="flex justify-between items-start mb-2">
<!-- Icon -->
<img src="../../images/icon-01.svg" width="32" height="32" alt="Icon 01" />
<EditMenu align="right" class="relative inline-flex">
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 1</a>
</li>
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 2</a>
</li>
<li>
<a class="font-medium text-sm text-red-500 hover:text-red-600 flex py-1 px-3" href="#0">Remove</a>
</li>
</EditMenu>
</header>
<h2 class="text-lg font-semibold text-gray-800 mb-2">Acme Plus</h2>
<div class="text-xs font-semibold text-gray-400 uppercase mb-1">Sales</div>
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">$24,780</div>
<div class="text-sm font-semibold text-white px-1.5 bg-green-500 rounded-full">+49%</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<!-- Change the height attribute to adjust the chart height -->
<LineChart :data="chartData" width="389" height="128" />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import LineChart from '../../charts/LineChart01.vue'
import EditMenu from '../../components/DropdownEditMenu.vue'
// Import utilities
import { tailwindConfig, hexToRGB } from '../../utils/Utils'
export default {
name: 'DashboardCard01',
components: {
LineChart,
EditMenu,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
'06-01-2021', '07-01-2021', '08-01-2021',
'09-01-2021', '10-01-2021', '11-01-2021',
'12-01-2021', '01-01-2022', '02-01-2022',
'03-01-2022', '04-01-2022', '05-01-2022',
'06-01-2022', '07-01-2022', '08-01-2022',
'09-01-2022', '10-01-2022', '11-01-2022',
'12-01-2022', '01-01-2023',
],
datasets: [
// Indigo line
{
data: [
732, 610, 610, 504, 504, 504, 349,
349, 504, 342, 504, 610, 391, 192,
154, 273, 191, 191, 126, 263, 349,
252, 423, 622, 470, 532,
],
fill: true,
backgroundColor: `rgba(${hexToRGB(tailwindConfig().theme.colors.blue[500])}, 0.08)`,
borderColor: tailwindConfig().theme.colors.indigo[500],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.indigo[500],
clip: 20,
},
// Gray line
{
data: [
532, 532, 532, 404, 404, 314, 314,
314, 314, 314, 234, 314, 234, 234,
314, 314, 314, 388, 314, 202, 202,
202, 202, 314, 720, 642,
],
borderColor: tailwindConfig().theme.colors.gray[300],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.gray[300],
clip: 20,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,105 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 xl:col-span-4 bg-white shadow-lg rounded-sm border border-gray-200">
<div class="px-5 pt-5">
<header class="flex justify-between items-start mb-2">
<!-- Icon -->
<img src="../../images/icon-02.svg" width="32" height="32" alt="Icon 02" />
<EditMenu align="right" class="relative inline-flex">
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 1</a>
</li>
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 2</a>
</li>
<li>
<a class="font-medium text-sm text-red-500 hover:text-red-600 flex py-1 px-3" href="#0">Remove</a>
</li>
</EditMenu>
</header>
<h2 class="text-lg font-semibold text-gray-800 mb-2">Acme Advanced</h2>
<div class="text-xs font-semibold text-gray-400 uppercase mb-1">Sales</div>
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">$17,489</div>
<div class="text-sm font-semibold text-white px-1.5 bg-yellow-500 rounded-full">-14%</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<!-- Change the height attribute to adjust the chart height -->
<LineChart :data="chartData" width="389" height="128" />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import LineChart from '../../charts/LineChart01.vue'
import Icon from '../../images/icon-02.svg'
import EditMenu from '../../components/DropdownEditMenu.vue'
// Import utilities
import { tailwindConfig, hexToRGB } from '../../utils/Utils'
export default {
name: 'DashboardCard01',
components: {
LineChart,
EditMenu,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
'06-01-2021', '07-01-2021', '08-01-2021',
'09-01-2021', '10-01-2021', '11-01-2021',
'12-01-2021', '01-01-2022', '02-01-2022',
'03-01-2022', '04-01-2022', '05-01-2022',
'06-01-2022', '07-01-2022', '08-01-2022',
'09-01-2022', '10-01-2022', '11-01-2022',
'12-01-2022', '01-01-2023',
],
datasets: [
// Indigo line
{
data: [
622, 622, 426, 471, 365, 365, 238,
324, 288, 206, 324, 324, 500, 409,
409, 273, 232, 273, 500, 570, 767,
808, 685, 767, 685, 685,
],
fill: true,
backgroundColor: `rgba(${hexToRGB(tailwindConfig().theme.colors.blue[500])}, 0.08)`,
borderColor: tailwindConfig().theme.colors.indigo[500],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.indigo[500],
clip: 20,
},
// Gray line
{
data: [
732, 610, 610, 504, 504, 504, 349,
349, 504, 342, 504, 610, 391, 192,
154, 273, 191, 191, 126, 263, 349,
252, 423, 622, 470, 532,
],
borderColor: tailwindConfig().theme.colors.gray[300],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.gray[300],
clip: 20,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,105 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 xl:col-span-4 bg-white shadow-lg rounded-sm border border-gray-200">
<div class="px-5 pt-5">
<header class="flex justify-between items-start mb-2">
<!-- Icon -->
<img src="../../images/icon-03.svg" width="32" height="32" alt="Icon 03" />
<EditMenu align="right" class="relative inline-flex">
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 1</a>
</li>
<li>
<a class="font-medium text-sm text-gray-600 hover:text-gray-800 flex py-1 px-3" href="#0">Option 2</a>
</li>
<li>
<a class="font-medium text-sm text-red-500 hover:text-red-600 flex py-1 px-3" href="#0">Remove</a>
</li>
</EditMenu>
</header>
<h2 class="text-lg font-semibold text-gray-800 mb-2">Acme Professional</h2>
<div class="text-xs font-semibold text-gray-400 uppercase mb-1">Sales</div>
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">$9,962</div>
<div class="text-sm font-semibold text-white px-1.5 bg-green-500 rounded-full">+29%</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<!-- Change the height attribute to adjust the chart height -->
<LineChart :data="chartData" width="389" height="128" />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import LineChart from '../../charts/LineChart01.vue'
import Icon from '../../images/icon-03.svg'
import EditMenu from '../../components/DropdownEditMenu.vue'
// Import utilities
import { tailwindConfig, hexToRGB } from '../../utils/Utils'
export default {
name: 'DashboardCard01',
components: {
LineChart,
EditMenu,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
'06-01-2021', '07-01-2021', '08-01-2021',
'09-01-2021', '10-01-2021', '11-01-2021',
'12-01-2021', '01-01-2022', '02-01-2022',
'03-01-2022', '04-01-2022', '05-01-2022',
'06-01-2022', '07-01-2022', '08-01-2022',
'09-01-2022', '10-01-2022', '11-01-2022',
'12-01-2022', '01-01-2023',
],
datasets: [
// Indigo line
{
data: [
540, 466, 540, 466, 385, 432, 334,
334, 289, 289, 200, 289, 222, 289,
289, 403, 554, 304, 289, 270, 134,
270, 829, 344, 388, 364,
],
fill: true,
backgroundColor: `rgba(${hexToRGB(tailwindConfig().theme.colors.blue[500])}, 0.08)`,
borderColor: tailwindConfig().theme.colors.indigo[500],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.indigo[500],
clip: 20,
},
// Gray line
{
data: [
689, 562, 477, 477, 477, 477, 458,
314, 430, 378, 430, 498, 642, 350,
145, 145, 354, 260, 188, 188, 300,
300, 282, 364, 660, 554,
],
borderColor: tailwindConfig().theme.colors.gray[300],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.gray[300],
clip: 20,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Direct VS Indirect</h2>
</header>
<!-- Chart built with Chart.js 3 -->
<!-- Change the height attribute to adjust the chart height -->
<BarChart :data="chartData" width="595" height="248" />
</div>
</template>
<script>
import { ref } from 'vue'
import BarChart from '../../charts/BarChart01.vue'
// Import utilities
import { tailwindConfig } from '../../utils/Utils'
export default {
name: 'DashboardCard03',
components: {
BarChart,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
],
datasets: [
// Light blue bars
{
label: 'Direct',
data: [
800, 1600, 900, 1300, 1950, 1700,
],
backgroundColor: tailwindConfig().theme.colors.blue[400],
hoverBackgroundColor: tailwindConfig().theme.colors.blue[500],
barPercentage: 0.66,
categoryPercentage: 0.66,
},
// Blue bars
{
label: 'Indirect',
data: [
4900, 2600, 5350, 4800, 5200, 4800,
],
backgroundColor: tailwindConfig().theme.colors.indigo[500],
hoverBackgroundColor: tailwindConfig().theme.colors.indigo[600],
barPercentage: 0.66,
categoryPercentage: 0.66,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100 flex items-center">
<h2 class="font-semibold text-gray-800">Real Time Value</h2>
<Tooltip class="ml-2">
<div class="text-xs text-center whitespace-nowrap">Built with <a class="underline" href="https://www.chartjs.org/" target="_blank" rel="noreferrer">Chart.js</a></div>
</Tooltip>
</header>
<!-- Chart built with Chart.js 3 -->
<!-- Change the height attribute to adjust the chart height -->
<RealtimeChart :data="chartData" width="595" height="248" />
</div>
</template>
<script>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import Tooltip from '../../components/Tooltip.vue'
import RealtimeChart from '../../charts/RealtimeChart.vue'
// Import utilities
import { tailwindConfig, hexToRGB } from '../../utils/Utils'
export default {
name: 'DashboardCard05',
components: {
Tooltip,
RealtimeChart,
},
setup() {
// IMPORTANT:
// Code below is for demo purpose only, and it's not covered by support.
// If you need to replace dummy data with real data,
// refer to Chart.js documentation: https://www.chartjs.org/docs/latest
const counter = ref(0)
const range = ref(35)
// Dummy data to be looped
const sampleData = [
57.81, 57.75, 55.48, 54.28, 53.14, 52.25, 51.04, 52.49, 55.49, 56.87,
53.73, 56.42, 58.06, 55.62, 58.16, 55.22, 58.67, 60.18, 61.31, 63.25,
65.91, 64.44, 65.97, 62.27, 60.96, 59.34, 55.07, 59.85, 53.79, 51.92,
50.95, 49.65, 48.09, 49.81, 47.85, 49.52, 50.21, 52.22, 54.42, 53.42,
50.91, 58.52, 53.37, 57.58, 59.09, 59.36, 58.71, 59.42, 55.93, 57.71,
50.62, 56.28, 57.37, 53.08, 55.94, 55.82, 53.94, 52.65, 50.25,
]
const slicedData = ref(sampleData.slice(0, range.value))
// Generate fake dates from now to back in time
const generateDates = () => {
const now = new Date()
const dates = []
sampleData.forEach((v, i) => {
dates.push(new Date(now - 2000 - i * 2000))
})
return dates
}
const slicedLabels = ref(generateDates().slice(0, range.value).reverse())
// Fake update every 2 seconds
const interval = ref(null)
onMounted(() => {
interval.value = setInterval(() => {
counter.value++
}, 2000)
})
onUnmounted(() => {
clearInterval(interval)
})
// Loop through data array and update
watch(counter, () => {
range.value++;
if (range.value >= sampleData.length) {
range.value = 0;
}
slicedData.value.shift();
slicedData.value.push(sampleData[range.value]);
slicedLabels.value.shift()
slicedLabels.value.push(new Date())
})
const chartData = computed(() => {
return {
labels: slicedLabels.value,
datasets: [
{
data: [...slicedData.value],
fill: true,
backgroundColor: `rgba(${hexToRGB(tailwindConfig().theme.colors.blue[500])}, 0.08)`,
borderColor: tailwindConfig().theme.colors.indigo[500],
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.indigo[500],
clip: 20,
},
],
}
})
return {
counter,
range,
slicedData,
slicedLabels,
interval,
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 xl:col-span-4 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Top Countries</h2>
</header>
<!-- Chart built with Chart.js 3 -->
<!-- Change the height attribute to adjust the chart height -->
<DoughnutChart :data="chartData" width="389" height="260" />
</div>
</template>
<script>
import { ref } from 'vue'
import DoughnutChart from '../../charts/DoughnutChart.vue'
import EditMenu from '../../components/DropdownEditMenu.vue'
// Import utilities
import { tailwindConfig } from '../../utils/Utils'
export default {
name: 'DashboardCard06',
components: {
DoughnutChart,
EditMenu,
},
setup() {
const chartData = ref({
labels: ['United States', 'Italy', 'Other'],
datasets: [
{
label: 'Top Countries',
data: [
35, 30, 35,
],
backgroundColor: [
tailwindConfig().theme.colors.indigo[500],
tailwindConfig().theme.colors.blue[400],
tailwindConfig().theme.colors.indigo[800],
],
hoverBackgroundColor: [
tailwindConfig().theme.colors.indigo[600],
tailwindConfig().theme.colors.blue[500],
tailwindConfig().theme.colors.indigo[900],
],
hoverBorderColor: tailwindConfig().theme.colors.white,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,165 @@
<template>
<div class="col-span-full xl:col-span-8 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Top Channels</h2>
</header>
<div class="p-3">
<!-- Table -->
<div class="overflow-x-auto">
<table class="table-auto w-full">
<!-- Table header -->
<thead class="text-xs uppercase text-gray-400 bg-gray-50 rounded-sm">
<tr>
<th class="p-2">
<div class="font-semibold text-left">Source</div>
</th>
<th class="p-2">
<div class="font-semibold text-center">Visitors</div>
</th>
<th class="p-2">
<div class="font-semibold text-center">Revenues</div>
</th>
<th class="p-2">
<div class="font-semibold text-center">Sales</div>
</th>
<th class="p-2">
<div class="font-semibold text-center">Conversion</div>
</th>
</tr>
</thead>
<!-- Table body -->
<tbody class="text-sm font-medium divide-y divide-gray-100">
<!-- Row -->
<tr>
<td class="p-2">
<div class="flex items-center">
<svg class="flex-shrink-0 mr-2 sm:mr-3" width="36" height="36" viewBox="0 0 36 36">
<circle fill="#24292E" cx="18" cy="18" r="18" />
<path d="M18 10.2c-4.4 0-8 3.6-8 8 0 3.5 2.3 6.5 5.5 7.6.4.1.5-.2.5-.4V24c-2.2.5-2.7-1-2.7-1-.4-.9-.9-1.2-.9-1.2-.7-.5.1-.5.1-.5.8.1 1.2.8 1.2.8.7 1.3 1.9.9 2.3.7.1-.5.3-.9.5-1.1-1.8-.2-3.6-.9-3.6-4 0-.9.3-1.6.8-2.1-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.2.8.6-.2 1.3-.3 2-.3s1.4.1 2 .3c1.5-1 2.2-.8 2.2-.8.4 1.1.2 1.9.1 2.1.5.6.8 1.3.8 2.1 0 3.1-1.9 3.7-3.7 3.9.3.4.6.9.6 1.6v2.2c0 .2.1.5.6.4 3.2-1.1 5.5-4.1 5.5-7.6-.1-4.4-3.7-8-8.1-8z" fill="#FFF" />
</svg>
<div class="text-gray-800">Github.com</div>
</div>
</td>
<td class="p-2">
<div class="text-center">2.4K</div>
</td>
<td class="p-2">
<div class="text-center text-green-500">$3,877</div>
</td>
<td class="p-2">
<div class="text-center">267</div>
</td>
<td class="p-2">
<div class="text-center text-light-blue-500">4.7%</div>
</td>
</tr>
<!-- Row -->
<tr>
<td class="p-2">
<div class="flex items-center">
<svg class="flex-shrink-0 mr-2 sm:mr-3" width="36" height="36" viewBox="0 0 36 36">
<circle fill="#1DA1F2" cx="18" cy="18" r="18" />
<path d="M26 13.5c-.6.3-1.2.4-1.9.5.7-.4 1.2-1 1.4-1.8-.6.4-1.3.6-2.1.8-.6-.6-1.5-1-2.4-1-1.7 0-3.2 1.5-3.2 3.3 0 .3 0 .5.1.7-2.7-.1-5.2-1.4-6.8-3.4-.3.5-.4 1-.4 1.7 0 1.1.6 2.1 1.5 2.7-.5 0-1-.2-1.5-.4 0 1.6 1.1 2.9 2.6 3.2-.3.1-.6.1-.9.1-.2 0-.4 0-.6-.1.4 1.3 1.6 2.3 3.1 2.3-1.1.9-2.5 1.4-4.1 1.4H10c1.5.9 3.2 1.5 5 1.5 6 0 9.3-5 9.3-9.3v-.4c.7-.5 1.3-1.1 1.7-1.8z" fill="#FFF" fill-rule="nonzero" />
</svg>
<div class="text-gray-800">Twitter</div>
</div>
</td>
<td class="p-2">
<div class="text-center">2.2K</div>
</td>
<td class="p-2">
<div class="text-center text-green-500">$3,426</div>
</td>
<td class="p-2">
<div class="text-center">249</div>
</td>
<td class="p-2">
<div class="text-center text-light-blue-500">4.4%</div>
</td>
</tr>
<!-- Row -->
<tr>
<td class="p-2">
<div class="flex items-center">
<svg class="flex-shrink-0 mr-2 sm:mr-3" width="36" height="36" viewBox="0 0 36 36">
<circle fill="#EA4335" cx="18" cy="18" r="18" />
<path d="M18 17v2.4h4.1c-.2 1-1.2 3-4 3-2.4 0-4.3-2-4.3-4.4 0-2.4 2-4.4 4.3-4.4 1.4 0 2.3.6 2.8 1.1l1.9-1.8C21.6 11.7 20 11 18.1 11c-3.9 0-7 3.1-7 7s3.1 7 7 7c4 0 6.7-2.8 6.7-6.8 0-.5 0-.8-.1-1.2H18z" fill="#FFF" fill-rule="nonzero" />
</svg>
<div class="text-gray-800">Google (organic)</div>
</div>
</td>
<td class="p-2">
<div class="text-center">2.0K</div>
</td>
<td class="p-2">
<div class="text-center text-green-500">$2,444</div>
</td>
<td class="p-2">
<div class="text-center">224</div>
</td>
<td class="p-2">
<div class="text-center text-light-blue-500">4.2%</div>
</td>
</tr>
<!-- Row -->
<tr>
<td class="p-2">
<div class="flex items-center">
<svg class="flex-shrink-0 mr-2 sm:mr-3" width="36" height="36" viewBox="0 0 36 36">
<circle fill="#4BC9FF" cx="18" cy="18" r="18" />
<path d="M26 14.3c-.1 1.6-1.2 3.7-3.3 6.4-2.2 2.8-4 4.2-5.5 4.2-.9 0-1.7-.9-2.4-2.6C14 19.9 13.4 15 12 15c-.1 0-.5.3-1.2.8l-.8-1c.8-.7 3.5-3.4 4.7-3.5 1.2-.1 2 .7 2.3 2.5.3 2 .8 6.1 1.8 6.1.9 0 2.5-3.4 2.6-4 .1-.9-.3-1.9-2.3-1.1.8-2.6 2.3-3.8 4.5-3.8 1.7.1 2.5 1.2 2.4 3.3z" fill="#FFF" fill-rule="nonzero" />
</svg>
<div class="text-gray-800">Vimeo.com</div>
</div>
</td>
<td class="p-2">
<div class="text-center">1.9K</div>
</td>
<td class="p-2">
<div class="text-center text-green-500">$2,236</div>
</td>
<td class="p-2">
<div class="text-center">220</div>
</td>
<td class="p-2">
<div class="text-center text-light-blue-500">4.2%</div>
</td>
</tr>
<!-- Row -->
<tr>
<td class="p-2">
<div class="flex items-center">
<svg class="flex-shrink-0 mr-2 sm:mr-3" width="36" height="36" viewBox="0 0 36 36">
<circle fill="#0E2439" cx="18" cy="18" r="18" />
<path d="M14.232 12.818V23H11.77V12.818h2.46zM15.772 23V12.818h2.462v4.087h4.012v-4.087h2.456V23h-2.456v-4.092h-4.012V23h-2.461z" fill="#E6ECF4" />
</svg>
<div class="text-gray-800">Indiehackers.com</div>
</div>
</td>
<td class="p-2">
<div class="text-center">1.7K</div>
</td>
<td class="p-2">
<div class="text-center text-green-500">$2,034</div>
</td>
<td class="p-2">
<div class="text-center">204</div>
</td>
<td class="p-2">
<div class="text-center text-light-blue-500">3.9%</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DashboardCard07',
}
</script>

View File

@@ -0,0 +1,97 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100 flex items-center">
<h2 class="font-semibold text-gray-800">Sales Over Time (all stores)</h2>
</header>
<!-- Chart built with Chart.js 3 -->
<!-- Change the height attribute to adjust the chart height -->
<LineChart :data="chartData" width="595" height="248" />
</div>
</template>
<script>
import { ref } from 'vue'
import LineChart from '../../charts/LineChart02.vue'
// Import utilities
import { tailwindConfig } from '../../utils/Utils'
export default {
name: 'DashboardCard08',
components: {
LineChart,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
'06-01-2021', '07-01-2021', '08-01-2021',
'09-01-2021', '10-01-2021', '11-01-2021',
'12-01-2021', '01-01-2022', '02-01-2022',
'03-01-2022', '04-01-2022', '05-01-2022',
'06-01-2022', '07-01-2022', '08-01-2022',
'09-01-2022', '10-01-2022', '11-01-2022',
'12-01-2022', '01-01-2023',
],
datasets: [
// Indigo line
{
label: 'Current',
data: [
73, 64, 73, 69, 104, 104, 164,
164, 120, 120, 120, 148, 142, 104,
122, 110, 104, 152, 166, 233, 268,
252, 284, 284, 333, 323,
],
borderColor: tailwindConfig().theme.colors.indigo[500],
fill: false,
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.indigo[500],
},
// Blue line
{
label: 'Previous',
data: [
184, 86, 42, 378, 42, 243, 38,
120, 0, 0, 42, 0, 84, 0,
276, 0, 124, 42, 124, 88, 88,
215, 156, 88, 124, 64,
],
borderColor: tailwindConfig().theme.colors.blue[400],
fill: false,
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.blue[400],
},
// Green line
{
label: 'Average',
data: [
122, 170, 192, 86, 102, 124, 115,
115, 56, 104, 0, 72, 208, 186,
223, 188, 114, 162, 200, 150, 118,
118, 76, 122, 230, 268,
],
borderColor: tailwindConfig().theme.colors.green[500],
fill: false,
borderWidth: 2,
tension: 0,
pointRadius: 0,
pointHoverRadius: 3,
pointBackgroundColor: tailwindConfig().theme.colors.green[500],
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,74 @@
<template>
<div class="flex flex-col col-span-full sm:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100 flex items-center">
<h2 class="font-semibold text-gray-800">Sales VS Refunds</h2>
<Tooltip class="ml-2" size="lg">
<div class="text-sm">Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit.</div>
</Tooltip>
</header>
<div class="px-5 py-3">
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">+$6,796</div>
<div class="text-sm font-semibold text-white px-1.5 bg-yellow-500 rounded-full">-34%</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<!-- Change the height attribute to adjust the chart height -->
<BarChart :data="chartData" width="595" height="248" />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import Tooltip from '../../components/Tooltip.vue'
import BarChart from '../../charts/BarChart02.vue'
// Import utilities
import { tailwindConfig } from '../../utils/Utils'
export default {
name: 'DashboardCard09',
components: {
Tooltip,
BarChart,
},
setup() {
const chartData = ref({
labels: [
'12-01-2020', '01-01-2021', '02-01-2021',
'03-01-2021', '04-01-2021', '05-01-2021',
],
datasets: [
// Light blue bars
{
label: 'Stack 1',
data: [
6200, 9200, 6600, 8800, 5200, 9200,
],
backgroundColor: tailwindConfig().theme.colors.indigo[500],
hoverBackgroundColor: tailwindConfig().theme.colors.indigo[600],
barPercentage: 0.66,
categoryPercentage: 0.66,
},
// Blue bars
{
label: 'Stack 2',
data: [
-4000, -2600, -5350, -4000, -7500, -2000,
],
backgroundColor: tailwindConfig().theme.colors.indigo[200],
hoverBackgroundColor: tailwindConfig().theme.colors.indigo[300],
barPercentage: 0.66,
categoryPercentage: 0.66,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,121 @@
<template>
<div class="col-span-full xl:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Customers</h2>
</header>
<div class="p-3">
<!-- Table -->
<div class="overflow-x-auto">
<table class="table-auto w-full">
<!-- Table header -->
<thead class="text-xs font-semibold uppercase text-gray-400 bg-gray-50">
<tr>
<th class="p-2 whitespace-nowrap">
<div class="font-semibold text-left">Name</div>
</th>
<th class="p-2 whitespace-nowrap">
<div class="font-semibold text-left">Email</div>
</th>
<th class="p-2 whitespace-nowrap">
<div class="font-semibold text-left">Spent</div>
</th>
<th class="p-2 whitespace-nowrap">
<div class="font-semibold text-left">Country</div>
</th>
</tr>
</thead>
<!-- Table body -->
<tbody class="text-sm divide-y divide-gray-100">
<tr
v-for="customer in customers"
:key="customer.id"
>
<td class="p-2 whitespace-nowrap">
<div class="flex items-center">
<div class="w-10 h-10 flex-shrink-0 mr-2 sm:mr-3">
<img class="rounded-full" :src="customer.image" width="40" height="40" :alt="customer.name" />
</div>
<div class="font-medium text-gray-800">{{customer.name}}</div>
</div>
</td>
<td class="p-2 whitespace-nowrap">
<div class="text-left">{{customer.email}}</div>
</td>
<td class="p-2 whitespace-nowrap">
<div class="text-left font-medium text-green-500">{{customer.spent}}</div>
</td>
<td class="p-2 whitespace-nowrap">
<div class="text-lg text-center">{{customer.location}}</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import Image01 from '../../images/user-36-05.jpg'
import Image02 from '../../images/user-36-06.jpg'
import Image03 from '../../images/user-36-07.jpg'
import Image04 from '../../images/user-36-08.jpg'
import Image05 from '../../images/user-36-09.jpg'
export default {
name: 'DashboardCard10',
setup() {
const customers = ref([
{
id: '0',
image: Image01,
name: 'Alex Shatov',
email: 'alexshatov@gmail.com',
location: '🇺🇸',
spent: '$2,890.66',
},
{
id: '1',
image: Image02,
name: 'Philip Harbach',
email: 'philip.h@gmail.com',
location: '🇩🇪',
spent: '$2,767.04',
},
{
id: '2',
image: Image03,
name: 'Mirko Fisuk',
email: 'mirkofisuk@gmail.com',
location: '🇫🇷',
spent: '$2,996.00',
},
{
id: '3',
image: Image04,
name: 'Olga Semklo',
email: 'olga.s@cool.design',
location: '🇮🇹',
spent: '$1,220.66',
},
{
id: '4',
image: Image05,
name: 'Burak Long',
email: 'longburak@gmail.com',
location: '🇬🇧',
spent: '$1,890.66',
},
])
return {
customers,
}
}
}
</script>

View File

@@ -0,0 +1,84 @@
<template>
<div class="col-span-full xl:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Reason for Refunds</h2>
</header>
<div class="px-5 py-3">
<div class="flex items-start">
<div class="text-3xl font-bold text-gray-800 mr-2">449</div>
<div class="text-sm font-semibold text-white px-1.5 bg-yellow-500 rounded-full">-22%</div>
</div>
</div>
<!-- Chart built with Chart.js 3 -->
<div class="flex-grow">
<!-- Change the height attribute to adjust the chart height -->
<BarChart :data="chartData" :width="595" :height="48" />
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import BarChart from '../../charts/BarChart03.vue'
// Import utilities
import { tailwindConfig } from '../../utils/Utils'
export default {
name: 'DashboardCard11',
components: {
BarChart,
},
setup() {
const chartData = ref({
labels: ['Reasons'],
datasets: [
{
label: 'Having difficulties using the product',
data: [131],
backgroundColor: tailwindConfig().theme.colors.indigo[500],
hoverBackgroundColor: tailwindConfig().theme.colors.indigo[600],
barPercentage: 1,
categoryPercentage: 1,
},
{
label: 'Missing features I need',
data: [100],
backgroundColor: tailwindConfig().theme.colors.indigo[800],
hoverBackgroundColor: tailwindConfig().theme.colors.indigo[900],
barPercentage: 1,
categoryPercentage: 1,
},
{
label: 'Not satisfied about the quality of the product',
data: [81],
backgroundColor: tailwindConfig().theme.colors['light-blue'][400],
hoverBackgroundColor: tailwindConfig().theme.colors['light-blue'][500],
barPercentage: 1,
categoryPercentage: 1,
},
{
label: 'The product doesnt look as advertised',
data: [65],
backgroundColor: tailwindConfig().theme.colors.green[400],
hoverBackgroundColor: tailwindConfig().theme.colors.green[500],
barPercentage: 1,
categoryPercentage: 1,
},
{
label: 'Other',
data: [72],
backgroundColor: tailwindConfig().theme.colors.gray[200],
hoverBackgroundColor: tailwindConfig().theme.colors.gray[300],
barPercentage: 1,
categoryPercentage: 1,
},
],
})
return {
chartData,
}
}
}
</script>

View File

@@ -0,0 +1,110 @@
<template>
<div class="col-span-full xl:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Recent Activity</h2>
</header>
<div class="p-3">
<!-- Card content -->
<!-- "Today" group -->
<div>
<header class="text-xs uppercase text-gray-400 bg-gray-50 rounded-sm font-semibold p-2">Today</header>
<ul class="my-1">
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-indigo-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-indigo-50" viewBox="0 0 36 36">
<path d="M18 10c-4.4 0-8 3.1-8 7s3.6 7 8 7h.6l5.4 2v-4.4c1.2-1.2 2-2.8 2-4.6 0-3.9-3.6-7-8-7zm4 10.8v2.3L18.9 22H18c-3.3 0-6-2.2-6-5s2.7-5 6-5 6 2.2 6 5c0 2.2-2 3.8-2 3.8z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Nick Mark</a> mentioned <a class="font-medium text-gray-800" href="#0">Sara Smith</a> in a new post</div>
<div class="flex-shrink-0 self-end ml-2">
<a class="font-medium text-indigo-500 hover:text-indigo-600" href="#0">View<span class="hidden sm:inline"> -&gt;</span></a>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-red-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-red-50" viewBox="0 0 36 36">
<path d="M25 24H11a1 1 0 01-1-1v-5h2v4h12v-4h2v5a1 1 0 01-1 1zM14 13h8v2h-8z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center">The post <a class="font-medium text-gray-800" href="#0">Post Name</a> was removed by <a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Nick Mark</a></div>
<div class="flex-shrink-0 self-end ml-2">
<a class="font-medium text-indigo-500 hover:text-indigo-600" href="#0">View<span class="hidden sm:inline"> -&gt;</span></a>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-green-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-green-50" viewBox="0 0 36 36">
<path d="M15 13v-3l-5 4 5 4v-3h8a1 1 0 000-2h-8zM21 21h-8a1 1 0 000 2h8v3l5-4-5-4v3z" />
</svg>
</div>
<div class="flex-grow flex items-center text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Patrick Sullivan</a> published a new <a class="font-medium text-gray-800" href="#0">post</a></div>
<div class="flex-shrink-0 self-end ml-2">
<a class="font-medium text-indigo-500 hover:text-indigo-600" href="#0">View<span class="hidden sm:inline"> -&gt;</span></a>
</div>
</div>
</div>
</li>
</ul>
</div>
<!-- "Yesterday" group -->
<div>
<header class="text-xs uppercase text-gray-400 bg-gray-50 rounded-sm font-semibold p-2">Yesterday</header>
<ul class="my-1">
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-light-blue-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-light-blue-50" viewBox="0 0 36 36">
<path d="M23 11v2.085c-2.841.401-4.41 2.462-5.8 4.315-1.449 1.932-2.7 3.6-5.2 3.6h-1v2h1c3.5 0 5.253-2.338 6.8-4.4 1.449-1.932 2.7-3.6 5.2-3.6h3l-4-4zM15.406 16.455c.066-.087.125-.162.194-.254.314-.419.656-.872 1.033-1.33C15.475 13.802 14.038 13 12 13h-1v2h1c1.471 0 2.505.586 3.406 1.455zM24 21c-1.471 0-2.505-.586-3.406-1.455-.066.087-.125.162-.194.254-.316.422-.656.873-1.028 1.328.959.878 2.108 1.573 3.628 1.788V25l4-4h-3z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">240+</a> users have subscribed to <a class="font-medium text-gray-800" href="#0">Newsletter #1</a></div>
<div class="flex-shrink-0 self-end ml-2">
<a class="font-medium text-indigo-500 hover:text-indigo-600" href="#0">View<span class="hidden sm:inline"> -&gt;</span></a>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-indigo-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-indigo-50" viewBox="0 0 36 36">
<path d="M18 10c-4.4 0-8 3.1-8 7s3.6 7 8 7h.6l5.4 2v-4.4c1.2-1.2 2-2.8 2-4.6 0-3.9-3.6-7-8-7zm4 10.8v2.3L18.9 22H18c-3.3 0-6-2.2-6-5s2.7-5 6-5 6 2.2 6 5c0 2.2-2 3.8-2 3.8z" />
</svg>
</div>
<div class="flex-grow flex items-center text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center">The post <a class="font-medium text-gray-800" href="#0">Post Name</a> was suspended by <a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Nick Mark</a></div>
<div class="flex-shrink-0 self-end ml-2">
<a class="font-medium text-indigo-500 hover:text-indigo-600" href="#0">View<span class="hidden sm:inline"> -&gt;</span></a>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DashboardCard12',
}
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div class="col-span-full xl:col-span-6 bg-white shadow-lg rounded-sm border border-gray-200">
<header class="px-5 py-4 border-b border-gray-100">
<h2 class="font-semibold text-gray-800">Income/Expenses</h2>
</header>
<div class="p-3">
<!-- Card content -->
<!-- "Today" group -->
<div>
<header class="text-xs uppercase text-gray-400 bg-gray-50 rounded-sm font-semibold p-2">Today</header>
<ul class="my-1">
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-red-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-red-50" viewBox="0 0 36 36">
<path d="M17.7 24.7l1.4-1.4-4.3-4.3H25v-2H14.8l4.3-4.3-1.4-1.4L11 18z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Qonto</a> billing</div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-gray-800">-$49.88</span>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-green-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-green-50" viewBox="0 0 36 36">
<path d="M18.3 11.3l-1.4 1.4 4.3 4.3H11v2h10.2l-4.3 4.3 1.4 1.4L25 18z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Cruip.com</a> Market Ltd 70 Wilson St London</div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-green-500">+249.88</span>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-green-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-green-50" viewBox="0 0 36 36">
<path d="M18.3 11.3l-1.4 1.4 4.3 4.3H11v2h10.2l-4.3 4.3 1.4 1.4L25 18z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Notion Labs Inc</a></div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-green-500">+99.99</span>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-green-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-green-50" viewBox="0 0 36 36">
<path d="M18.3 11.3l-1.4 1.4 4.3 4.3H11v2h10.2l-4.3 4.3 1.4 1.4L25 18z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">Market Cap Ltd</a></div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-green-500">+1,200.88</span>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-gray-200 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-gray-400" viewBox="0 0 36 36">
<path d="M21.477 22.89l-8.368-8.367a6 6 0 008.367 8.367zm1.414-1.413a6 6 0 00-8.367-8.367l8.367 8.367zM18 26a8 8 0 110-16 8 8 0 010 16z" />
</svg>
</div>
<div class="flex-grow flex items-center border-b border-gray-100 text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">App.com</a> Market Ltd 70 Wilson St London</div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-gray-800 line-through">+$99.99</span>
</div>
</div>
</div>
</li>
<!-- Item -->
<li class="flex px-2">
<div class="w-9 h-9 rounded-full flex-shrink-0 bg-red-500 my-2 mr-3">
<svg class="w-9 h-9 fill-current text-red-50" viewBox="0 0 36 36">
<path d="M17.7 24.7l1.4-1.4-4.3-4.3H25v-2H14.8l4.3-4.3-1.4-1.4L11 18z" />
</svg>
</div>
<div class="flex-grow flex items-center text-sm py-2">
<div class="flex-grow flex justify-between">
<div class="self-center"><a class="font-medium text-gray-800 hover:text-gray-900" href="#0">App.com</a> Market Ltd 70 Wilson St London</div>
<div class="flex-shrink-0 self-start ml-2">
<span class="font-medium text-gray-800">-$49.88</span>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DashboardCard13',
}
</script>

View File

@@ -0,0 +1,59 @@
<template>
<div class="relative bg-indigo-200 p-4 sm:p-6 rounded-sm overflow-hidden mb-8">
<!-- Background illustration -->
<div class="absolute right-0 top-0 -mt-4 mr-16 pointer-events-none hidden xl:block" aria-hidden="true">
<svg width="319" height="198" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="welcome-a" d="M64 0l64 128-64-20-64 20z" />
<path id="welcome-e" d="M40 0l40 80-40-12.5L0 80z" />
<path id="welcome-g" d="M40 0l40 80-40-12.5L0 80z" />
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="welcome-b">
<stop stop-color="#A5B4FC" offset="0%" />
<stop stop-color="#818CF8" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="24.537%" x2="50%" y2="100%" id="welcome-c">
<stop stop-color="#4338CA" offset="0%" />
<stop stop-color="#6366F1" stop-opacity="0" offset="100%" />
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<g transform="rotate(64 36.592 105.604)">
<mask id="welcome-d" fill="#fff">
<use xlink:href="#welcome-a" />
</mask>
<use fill="url(#welcome-b)" xlink:href="#welcome-a" />
<path fill="url(#welcome-c)" mask="url(#welcome-d)" d="M64-24h80v152H64z" />
</g>
<g transform="rotate(-51 91.324 -105.372)">
<mask id="welcome-f" fill="#fff">
<use xlink:href="#welcome-e" />
</mask>
<use fill="url(#welcome-b)" xlink:href="#welcome-e" />
<path fill="url(#welcome-c)" mask="url(#welcome-f)" d="M40.333-15.147h50v95h-50z" />
</g>
<g transform="rotate(44 61.546 392.623)">
<mask id="welcome-h" fill="#fff">
<use xlink:href="#welcome-g" />
</mask>
<use fill="url(#welcome-b)" xlink:href="#welcome-g" />
<path fill="url(#welcome-c)" mask="url(#welcome-h)" d="M40.333-15.147h50v95h-50z" />
</g>
</g>
</svg>
</div>
<!-- Content -->
<div class="relative">
<h1 class="text-2xl md:text-3xl text-gray-800 font-bold mb-1">Good afternoon, Acme Inc. 👋</h1>
<p>Here is whats happening with your projects today:</p>
</div>
</div>
</template>
<script>
export default {
name: 'WelcomeBanner',
}
</script>

16
src/router.js Normal file
View File

@@ -0,0 +1,16 @@
import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from './pages/Dashboard.vue'
const routerHistory = createWebHistory()
const router = createRouter({
history: routerHistory,
routes: [
{
path: '/',
component: Dashboard
},
]
})
export default router

35
src/utils/Utils.js Normal file
View File

@@ -0,0 +1,35 @@
import resolveConfig from 'tailwindcss/resolveConfig';
import tailwindConfigFile from '@tailwindConfig';
export const tailwindConfig = () => {
// Tailwind config
return resolveConfig(tailwindConfigFile)
}
export const hexToRGB = (h) => {
let r = 0;
let g = 0;
let b = 0;
if (h.length === 4) {
r = `0x${h[1]}${h[1]}`;
g = `0x${h[2]}${h[2]}`;
b = `0x${h[3]}${h[3]}`;
} else if (h.length === 7) {
r = `0x${h[1]}${h[2]}`;
g = `0x${h[3]}${h[4]}`;
b = `0x${h[5]}${h[6]}`;
}
return `${+r},${+g},${+b}`;
};
export const formatValue = (value) => Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumSignificantDigits: 3,
notation: 'compact',
}).format(value);
export const formatThousands = (value) => Intl.NumberFormat('en-US', {
maximumSignificantDigits: 3,
notation: 'compact',
}).format(value);

31
vite.config.js Normal file
View File

@@ -0,0 +1,31 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: [
{
find: /^~.+/,
replacement: (val) => {
return val.replace(/^~/, "");
},
},
{
find: '@tailwindConfig',
replacement: () => './src/css/tailwind.config.js',
}
],
},
optimizeDeps: {
include: [
'@tailwindConfig',
]
},
build: {
commonjsOptions: {
transformMixedEsModules: true,
}
}
})