create dashboard layout
This commit is contained in:
30
resources/js/Components/DashboardLayout/Sidebar.vue
Normal file
30
resources/js/Components/DashboardLayout/Sidebar.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
import Builder from './Sidebar/Builder.vue'
|
||||
import Icon from '@/Components/Icon.vue'
|
||||
|
||||
const self = getCurrentInstance()
|
||||
const menus = ref([])
|
||||
const { user } = usePage().props.value
|
||||
|
||||
const fetch = async () => {
|
||||
try {
|
||||
const response = await axios.get(route('api.v1.user.menu', user.id))
|
||||
return menus.value = response.data
|
||||
} catch (e) {
|
||||
setTimeout(fetch, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
fetch()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full bg-inherit">
|
||||
<Builder v-if="menus.length" :menus="menus" />
|
||||
</div>
|
||||
|
||||
<div class="hidden pl-8"></div>
|
||||
</template>
|
||||
30
resources/js/Components/DashboardLayout/Sidebar/Builder.vue
Normal file
30
resources/js/Components/DashboardLayout/Sidebar/Builder.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import { defineComponent, h } from "vue"
|
||||
import Link from "./Link.vue"
|
||||
import Links from "./Links.vue"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
menus: Array,
|
||||
},
|
||||
|
||||
setup(props, { attrs }) {
|
||||
return props => {
|
||||
const { menus } = props
|
||||
const generate = (menu, attrs = {}) => {
|
||||
if (menu.childs?.length > 0) {
|
||||
return h(Links, {
|
||||
...attrs,
|
||||
menu,
|
||||
childs: menu.childs,
|
||||
}, menu.childs.map(child => generate(child, { class: 'pl-8' })))
|
||||
}
|
||||
|
||||
return h(Link, { ...attrs, menu })
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex flex-col' }, menus.map(menu => generate(menu)))
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
23
resources/js/Components/DashboardLayout/Sidebar/Link.vue
Normal file
23
resources/js/Components/DashboardLayout/Sidebar/Link.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import { Link } from '@inertiajs/inertia-vue3'
|
||||
import Icon from '@/Components/Icon.vue'
|
||||
|
||||
const { menu } = defineProps({
|
||||
menu: Object,
|
||||
})
|
||||
|
||||
const active = route().current(menu.route_or_url)
|
||||
|
||||
if (route().has(menu.route_or_url))
|
||||
menu.route_or_url = route(menu.route_or_url)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="menu.route_or_url" class="w-full px-4 py-3" :class="`${themes().get('sidebar', 'bg-slate-700 text-gray-200')} ${active && 'bg-slate-800'}`">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Icon :name="menu.icon" />
|
||||
<p class="uppercase font-semibold">{{ menu.name }}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</template>
|
||||
41
resources/js/Components/DashboardLayout/Sidebar/Links.vue
Normal file
41
resources/js/Components/DashboardLayout/Sidebar/Links.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref } from 'vue'
|
||||
import Icon from '@/Components/Icon.vue'
|
||||
|
||||
const { menu, childs } = defineProps({
|
||||
menu: Object,
|
||||
childs: Array,
|
||||
})
|
||||
|
||||
const trace = menu => {
|
||||
if (menu.childs?.length) {
|
||||
for (const child of menu.child) {
|
||||
if (trace(child)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return route().current(menu.route_or_url)
|
||||
}
|
||||
|
||||
const active = childs.find(trace)
|
||||
const self = getCurrentInstance()
|
||||
const open = ref(active ? true : false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col">
|
||||
<button @click.prevent="open = ! open" class="w-full p-4" :class="`${themes().get('sidebar', 'bg-slate-700 text-gray-200')} ${open && 'dark:bg-gray-800'}`">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Icon :name="menu.icon" />
|
||||
<p class="uppercase font-semibold w-full text-left">{{ menu.name }}</p>
|
||||
<Icon name="caret-left" class="transition-all ease-in-out duration-150" :class="open && '-rotate-90'" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div v-if="open" class="flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
11
resources/js/Components/DashboardLayout/SidebarToggler.vue
Normal file
11
resources/js/Components/DashboardLayout/SidebarToggler.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import Icon from '../Icon.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-none w-14 h-14 p-3">
|
||||
<button @click.prevent="$emit('toggle')" type="button" class="w-full h-full rounded-md border dark:border-gray-600 text-white dark:text-gray-700 transition-all hover:scale-105 p-1">
|
||||
<Icon name="bars" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
55
resources/js/Components/DashboardLayout/TopbarDropdown.vue
Normal file
55
resources/js/Components/DashboardLayout/TopbarDropdown.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref } from 'vue'
|
||||
import { usePage, Link } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import Icon from '../Icon.vue'
|
||||
|
||||
const open = ref(false)
|
||||
const { user } = usePage().props.value
|
||||
|
||||
const logout = () => Inertia.post(route('logout'))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.slide-enter-active, .slide-leave-active {
|
||||
transition: all 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.slide-enter-from, .slide-leave-to {
|
||||
right: -15rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div ref="container" class="flex-none flex items-center justify-between space-x-2 w-full max-w-xs h-14 px-3">
|
||||
<img :src="user.profile_photo_url" :alt="user.name" class="flex-none rounded-full w-10 h-10">
|
||||
|
||||
<p class="font-semibold lowercase first-letter:capitalize truncate w-full">{{ user.name }}</p>
|
||||
|
||||
<div class="flex-none w-14 h-14 p-3">
|
||||
<button @click.prevent="open = ! open" class="rounded-md border dark:border-gray-600 w-full h-full text-white dark:text-gray-700 transition-all ease-in-out duration-150 hover:border-gray-700 hover:text-gray-900">
|
||||
<Icon name="caret-left" class="transition-all ease-linear duration-500" :class="open && '-rotate-90'" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="slide">
|
||||
<div v-if="open" class="fixed sm:right-4 top-12 w-full max-w-xl sm:w-48 bg-white dark:bg-gray-700 rounded-md shadow-xl">
|
||||
<Link :href="route('profile.show')" as="button" class="w-full border-l-8 border-transparent dark:hover:border-gray-600 px-4 py-2 rounded-t-md transition-all ease-linear duration-150 hover:bg-gray-800">
|
||||
<div class="flex items-center space-x-2 dark:text-white font-semibold">
|
||||
<Icon name="user" />
|
||||
|
||||
<p class="uppercase">profile</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<button @click.prevent="logout" class="w-full border-l-8 border-transparent dark:hover:border-gray-600 px-4 py-2 rounded-b-md transition-all ease-linear duration-150 hover:bg-gray-800">
|
||||
<div class="flex items-center space-x-2 dark:text-white font-semibold">
|
||||
<Icon name="door-open" />
|
||||
|
||||
<p class="uppercase">logout</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
16
resources/js/Components/Icon.vue
Normal file
16
resources/js/Components/Icon.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const { type, name } = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'fa',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'circle',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<i :class="`${type} fa-${name}`"></i>
|
||||
</template>
|
||||
42
resources/js/Layouts/DashboardLayout.vue
Normal file
42
resources/js/Layouts/DashboardLayout.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, onMounted, ref } from 'vue'
|
||||
import { Head } from '@inertiajs/inertia-vue3'
|
||||
import Toggler from '@/Components/DashboardLayout/SidebarToggler.vue'
|
||||
import TopbarDropdown from '@/Components/DashboardLayout/TopbarDropdown.vue'
|
||||
import Sidebar from '@/Components/DashboardLayout/Sidebar.vue';
|
||||
|
||||
const { title } = defineProps({
|
||||
title: String,
|
||||
})
|
||||
|
||||
const self = getCurrentInstance()
|
||||
const open = ref(window.innerWidth > 669)
|
||||
|
||||
onMounted(() => window.addEventListener('resize', () => open.value = window.innerWidth > 669))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex bg-gray-200 dark:bg-gray-900 w-full h-screen font-nunito">
|
||||
<Head :title="title" />
|
||||
|
||||
<div class="flex-none flex flex-col transition-all ease-in-out duration-300" :class="`${themes().get('sidebar', 'bg-gray-700 text-gray-200 hover:bg-gray-800 hover:text-gray-100 transition-all ease-in-out duration-150').replace(/hover:(bg|text)-(.*?)-(\d+)/)} ${open ? 'w-60' : 'w-0'}`">
|
||||
<div class="flex-none flex items-center justify-between w-full h-14 px-2" :class="themes().get('topbar', 'bg-cyan-500 text-gray-700 hover:bg-cyan-600 hover:text-gray-800 transition-all ease-in-out duration-150').replace(/hover:(bg|text)-(.*?)-(\d+)/, '')">
|
||||
<Toggler @toggle="open = ! open" class="sm:hidden" />
|
||||
|
||||
<h1 class="text-2xl text-center font-bold w-full">Template</h1>
|
||||
|
||||
<Toggler class="sm:hidden" />
|
||||
</div>
|
||||
|
||||
<Sidebar v-if="open" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full h-screen">
|
||||
<div class="flex-none flex justify-between w-full h-14 px-2" :class="themes().get('topbar', 'bg-cyan-500 text-gray-700 hover:bg-cyan-600 hover:text-gray-800 transition-all ease-in-out duration-150').replace(/hover:(bg|text)-(.*?)-(\d+)/, '')">
|
||||
<Toggler @toggle="open = ! open" />
|
||||
|
||||
<TopbarDropdown />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,22 +1,9 @@
|
||||
<script setup>
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
import Welcome from '@/Jetstream/Welcome.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Dashboard">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
Dashboard
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||
<Welcome />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
<DashboardLayout title="Dashboard">
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createInertiaApp } from '@inertiajs/inertia-vue3';
|
||||
import { InertiaProgress } from '@inertiajs/progress';
|
||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
|
||||
import Themes from './themes'
|
||||
|
||||
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
|
||||
|
||||
@@ -16,6 +17,11 @@ createInertiaApp({
|
||||
return createApp({ render: () => h(app, props) })
|
||||
.use(plugin)
|
||||
.use(ZiggyVue, Ziggy)
|
||||
.mixin({
|
||||
methods: {
|
||||
themes: () => Themes,
|
||||
},
|
||||
})
|
||||
.mount(el);
|
||||
},
|
||||
});
|
||||
|
||||
19
resources/js/themes.js
Normal file
19
resources/js/themes.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ref } from "vue"
|
||||
|
||||
localStorage.setItem('themes', localStorage.getItem('themes') || JSON.stringify({
|
||||
topbar: 'bg-cyan-500 text-gray-700 hover:bg-cyan-600 hover:text-gray-800 transition-all ease-in-out duration-150',
|
||||
sidebar: 'bg-gray-700 text-gray-200 hover:bg-gray-800 hover:text-gray-100 transition-all ease-in-out duration-150',
|
||||
}))
|
||||
|
||||
const themes = ref(JSON.parse(localStorage.getItem('themes')))
|
||||
|
||||
const set = (key, val) => {
|
||||
themes.value[key] = val
|
||||
localStorage.setItem('themes', JSON.stringify(themes.value))
|
||||
}
|
||||
|
||||
const get = (key, def) => themes.value[key] || def
|
||||
|
||||
export default {
|
||||
...themes.value, set, get
|
||||
}
|
||||
Reference in New Issue
Block a user