Added sheet for sidebar mobile
This commit is contained in:
18
app/components/global/sidebar/Footer.vue
Normal file
18
app/components/global/sidebar/Footer.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { PhInfo } from '@phosphor-icons/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="grow">
|
||||
<UserCTA />
|
||||
</div>
|
||||
|
||||
<UiButton size="icon" class="size-8 hover:bg-secondary hover:text-secondary-foreground" variant="ghost" as-child>
|
||||
<NuxtLink to="/about">
|
||||
<PhInfo size="18" />
|
||||
</NuxtLink>
|
||||
</UiButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
50
app/components/global/sidebar/Menu.vue
Normal file
50
app/components/global/sidebar/Menu.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { PanelsLeftBottom } from 'lucide-vue-next';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<menu class="min-w-32 pt-16 flex flex-col gap-0.5">
|
||||
<li class="w-full">
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8 gap-1.5 hover:bg-secondary w-full justify-start"
|
||||
as-child
|
||||
>
|
||||
<NuxtLink to="/my" active-class="active-link" exact-active-class="exact-link">
|
||||
<PanelsLeftBottom :size="18" />
|
||||
|
||||
<span class="text-[.9em]">
|
||||
{{ $t('ui.sidebarMenu.projects') }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</UiButton>
|
||||
</li>
|
||||
</menu>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active-link {
|
||||
color: var(--color-secondary-foreground);
|
||||
background-color: var(--color-secondary);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--color-primary) 33%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.exact-link {
|
||||
color: var(--color-primary-foreground);
|
||||
background-color: color-mix(in srgb, var(--color-primary) 66%, transparent);
|
||||
cursor: initial;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--color-primary) 66%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .exact-link {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,136 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhCompass, PhGlobeHemisphereEast, PhHurricane, PhInfo, PhX } from "@phosphor-icons/vue"
|
||||
import type { SidebarMenuActionType, SidebarMenuIcon } from "./SidebarProps";
|
||||
import { PanelsLeftBottom } from "lucide-vue-next"
|
||||
import { cn } from "@/lib/utils";
|
||||
import { breakpointsTailwind } from "@vueuse/core"
|
||||
import { PhInfo } from "@phosphor-icons/vue";
|
||||
|
||||
const { revealAdvancedSearch } = useCalendar()
|
||||
const { toggleSidebar } = useUiStore()
|
||||
const { currentMenu, isSidebarOpened } = storeToRefs(useUiStore())
|
||||
|
||||
function handleMenuItemAction(actionType: SidebarMenuActionType) {
|
||||
if (actionType === "event-search") {
|
||||
revealAdvancedSearch()
|
||||
}
|
||||
}
|
||||
|
||||
function computeMenuItemIcon(iconString: SidebarMenuIcon) {
|
||||
switch (iconString) {
|
||||
case "universe":
|
||||
return PhHurricane
|
||||
case "world":
|
||||
return PhGlobeHemisphereEast
|
||||
default:
|
||||
return PhCompass
|
||||
}
|
||||
}
|
||||
|
||||
const breakpoints = useBreakpoints(
|
||||
breakpointsTailwind
|
||||
)
|
||||
|
||||
// const sidebarRef = ref(null)
|
||||
|
||||
// onClickOutside(sidebarRef, () => {
|
||||
// isSidebarOpened.value = false
|
||||
// })
|
||||
const { isSidebarOpened } = storeToRefs(useUiStore())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav
|
||||
ref="sidebarRef"
|
||||
:class="cn(
|
||||
['md:relative md:isolate w-16 py-6 grid gap-4 grid-rows-[1fr_auto] bg-background justify-center md:transition-colors'], // Base appearance
|
||||
['max-md:hidden w-fit relative isolate p-2 grid gap-4 grid-rows-[1fr_auto] bg-background justify-center transition-colors'], // Base appearance
|
||||
['after:opacity-50 after:contrast-125 dark:after:opacity-75'], // After styling
|
||||
['border-r-[1px] border-r-border dark:border-r-border'], // Colours
|
||||
['max-md:justify-stretch max-md:px-4 max-md:py-4 max-md:absolute max-md:left-0 max-md:inset-0 max-md:z-50 max-md:w-40 max-md:max-w-full max-md:transition-all'], // Responsive behaviours
|
||||
{
|
||||
'max-md:-translate-x-40': !isSidebarOpened,
|
||||
'max-md:-translate-x-0': isSidebarOpened
|
||||
}
|
||||
)"
|
||||
>
|
||||
<menu class="flex flex-col gap-4 max-md:items-center">
|
||||
<li class="mb-12 mt-4 max-md:self-start">
|
||||
<UiButton
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="md:hidden size-9 border-background/30"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<PhX size="19" />
|
||||
</UiButton>
|
||||
</li>
|
||||
<SidebarMenu />
|
||||
|
||||
<li class="max-md:self-start">
|
||||
<UiTooltipProvider :delay-duration="50" :disabled="!breakpoints.md.value">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full max-md:hidden"
|
||||
as-child
|
||||
>
|
||||
<NuxtLink to="/explore">
|
||||
<PhCompass size="24" weight="fill" />
|
||||
</NuxtLink>
|
||||
</UiButton>
|
||||
<NuxtLink
|
||||
to="/explore"
|
||||
class="md:hidden flex items-center gap-[.6ch] underline-offset-4 hover:underline"
|
||||
>
|
||||
<PhCompass size="22" weight="fill" />
|
||||
|
||||
<span class="text-[.9em]">
|
||||
{{ $t('pages.explore.menuLabel') }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'" :side-offset="6">
|
||||
<p>
|
||||
{{ $t('pages.explore.menuLabel') }}
|
||||
</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li>
|
||||
|
||||
<ClientOnly>
|
||||
<li v-for="(item, i) in currentMenu" :key="i">
|
||||
<UiTooltipProvider :delay-duration="50">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
v-if="item.to"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
as-child
|
||||
>
|
||||
<NuxtLink :to="item.to">
|
||||
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||
</NuxtLink>
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="item.action"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
@click="handleMenuItemAction(item.action!)"
|
||||
>
|
||||
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'" :side-offset="6">
|
||||
<p>{{ item.tooltip }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li>
|
||||
</ClientOnly>
|
||||
</menu>
|
||||
|
||||
<UserCTA />
|
||||
<SidebarFooter />
|
||||
</nav>
|
||||
|
||||
<UiSheet v-model:open="isSidebarOpened">
|
||||
<UiSheetContent side="left" class="p-2 grid gap-4 grid-rows-[auto_1fr]">
|
||||
<UiSheetHeader />
|
||||
|
||||
<SidebarMenu />
|
||||
<SidebarFooter />
|
||||
|
||||
</UiSheetContent>
|
||||
</UiSheet>
|
||||
</template>
|
||||
|
||||
@@ -8,8 +8,9 @@ const { toggleSidebar } = useUiStore()
|
||||
<UiButton
|
||||
size="icon"
|
||||
variant="outline"
|
||||
class="size-7 border-border hover:bg-secondary hover:text-secondary-foreground"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<PhList size="19" weight="light" />
|
||||
<PhList size="14" weight="light" />
|
||||
</UiButton>
|
||||
</template>
|
||||
@@ -44,46 +44,43 @@ async function handleLogout() {
|
||||
console.log(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
type AvailableRoutes = "/my" | "/my/settings" | "/about"
|
||||
|
||||
function pushRoute(to: AvailableRoutes) {
|
||||
router.push({ path: to })
|
||||
|
||||
closeMenu()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UiDropdownMenu v-model:open="menuOpened">
|
||||
<UiDropdownMenuTrigger class="aspect-square">
|
||||
<TransitionGroup name="fade-group" appear>
|
||||
<UiAvatar
|
||||
v-if="user"
|
||||
id="user-avatar"
|
||||
class="ring-[.2rem] ring-primary hover:bg-accent hover:ring-accent transition-all cursor-pointer"
|
||||
:class="cn({ 'ring-accent bg-accent dark:bg-accent': menuOpened })"
|
||||
>
|
||||
<UiAvatarImage
|
||||
:src="userMeta?.avatar_url"
|
||||
:alt="userMeta?.full_name"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<UiAvatarFallback>
|
||||
{{ $t('ui.sidebarMenu.avatarFallback') }}
|
||||
</UiAvatarFallback>
|
||||
</UiAvatar>
|
||||
<UiButton
|
||||
v-else
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="ring-[.2rem] ring-primary hover:bg-accent hover:ring-accent border-none dark:bg-background rounded-full bg-primary transition-all cursor-pointer"
|
||||
:class="cn({ 'ring-accent bg-accent dark:bg-accent': menuOpened })"
|
||||
>
|
||||
<PhUserCircle size="24" />
|
||||
</UiButton>
|
||||
</TransitionGroup>
|
||||
<UiDropdownMenuTrigger as-child>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
class="w-full justify-start p-2 h-fit hover:text-secondary-foreground hover:bg-secondary"
|
||||
:class="cn({ 'bg-secondary': menuOpened })"
|
||||
>
|
||||
<TransitionGroup name="fade-group" appear>
|
||||
<div
|
||||
v-if="user"
|
||||
class="flex gap-2 items-center"
|
||||
>
|
||||
<UiAvatar
|
||||
id="user-avatar"
|
||||
size="sm"
|
||||
class="rounded-sm ring-[.2rem] ring-primary transition-all cursor-pointer"
|
||||
>
|
||||
<UiAvatarImage
|
||||
:src="userMeta?.avatar_url"
|
||||
:alt="userMeta?.full_name"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<UiAvatarFallback>
|
||||
{{ $t('ui.sidebarMenu.avatarFallback') }}
|
||||
</UiAvatarFallback>
|
||||
</UiAvatar>
|
||||
|
||||
<div class="text-xs">
|
||||
{{ userMeta?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</UiButton>
|
||||
</UiDropdownMenuTrigger>
|
||||
|
||||
<UiDropdownMenuContent class="w-72 p-0 pb-1" :align="'start'" :side="'top'" :side-offset="10" :align-offset="25" :collision-padding="40">
|
||||
@@ -91,15 +88,6 @@ function pushRoute(to: AvailableRoutes) {
|
||||
<p class="p-2 text-[.7em] opacity-75">
|
||||
{{ $t('ui.greeting', { user: user?.email }) }}
|
||||
</p>
|
||||
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my')">
|
||||
<PhUser size="20" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.profile') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
@@ -194,13 +182,6 @@ function pushRoute(to: AvailableRoutes) {
|
||||
</UiDropdownMenuPortal>
|
||||
</UiDropdownMenuSub>
|
||||
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/about')">
|
||||
<PhInfo size="20" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.about') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
|
||||
<template v-if="user">
|
||||
|
||||
Reference in New Issue
Block a user