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">
|
||||
|
||||
@@ -9,9 +9,9 @@ export const avatarVariant = cva(
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "h-10 w-10 text-xs",
|
||||
base: "h-16 w-16 text-2xl",
|
||||
lg: "h-32 w-32 text-5xl",
|
||||
sm: "size-6 text-xs",
|
||||
base: "size-14 text-2xl",
|
||||
lg: "size-24 text-5xl",
|
||||
},
|
||||
shape: {
|
||||
circle: "rounded-full",
|
||||
|
||||
18
app/components/ui/sheet/Sheet.vue
Normal file
18
app/components/ui/sheet/Sheet.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
|
||||
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot
|
||||
data-slot="sheet"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot />
|
||||
</DialogRoot>
|
||||
</template>
|
||||
15
app/components/ui/sheet/SheetClose.vue
Normal file
15
app/components/ui/sheet/SheetClose.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogCloseProps } from "reka-ui"
|
||||
import { DialogClose } from "reka-ui"
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose
|
||||
data-slot="sheet-close"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
||||
53
app/components/ui/sheet/SheetContent.vue
Normal file
53
app/components/ui/sheet/SheetContent.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
DialogContent,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import SheetOverlay from "./SheetOverlay.vue"
|
||||
|
||||
interface SheetContentProps extends DialogContentProps {
|
||||
class?: HTMLAttributes["class"]
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||
side: "right",
|
||||
})
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class", "side")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<SheetOverlay />
|
||||
<DialogContent
|
||||
data-slot="sheet-content"
|
||||
:class="cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
side === 'right'
|
||||
&& 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full border-l max-w-sm',
|
||||
side === 'left'
|
||||
&& 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-full border-r max-w-sm',
|
||||
side === 'top'
|
||||
&& 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
||||
side === 'bottom'
|
||||
&& 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
||||
props.class)"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
>
|
||||
<slot />
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
21
app/components/ui/sheet/SheetDescription.vue
Normal file
21
app/components/ui/sheet/SheetDescription.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogDescriptionProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogDescription } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
data-slot="sheet-description"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
16
app/components/ui/sheet/SheetFooter.vue
Normal file
16
app/components/ui/sheet/SheetFooter.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="sheet-footer"
|
||||
:class="cn('mt-auto flex flex-col gap-2 p-4', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
33
app/components/ui/sheet/SheetHeader.vue
Normal file
33
app/components/ui/sheet/SheetHeader.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
DialogClose
|
||||
} from "reka-ui"
|
||||
import { PhX } from "@phosphor-icons/vue";
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="sheet-header"
|
||||
:class="cn('flex flex-col gap-1.5 p-4', props.class)"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 disabled:pointer-events-none"
|
||||
as-child
|
||||
>
|
||||
<UiButton
|
||||
size="icon"
|
||||
variant="outline"
|
||||
class="size-7 border-border bg-background hover:bg-secondary hover:text-secondary-foreground"
|
||||
>
|
||||
<PhX size="14" />
|
||||
<span class="sr-only">{{ $t('ui.actions.close') }}</span>
|
||||
</UiButton>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</template>
|
||||
21
app/components/ui/sheet/SheetOverlay.vue
Normal file
21
app/components/ui/sheet/SheetOverlay.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogOverlay } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="sheet-overlay"
|
||||
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/70', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
||||
21
app/components/ui/sheet/SheetTitle.vue
Normal file
21
app/components/ui/sheet/SheetTitle.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogTitleProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogTitle } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
data-slot="sheet-title"
|
||||
:class="cn('text-foreground font-semibold', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
15
app/components/ui/sheet/SheetTrigger.vue
Normal file
15
app/components/ui/sheet/SheetTrigger.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogTriggerProps } from "reka-ui"
|
||||
import { DialogTrigger } from "reka-ui"
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTrigger
|
||||
data-slot="sheet-trigger"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</template>
|
||||
8
app/components/ui/sheet/index.ts
Normal file
8
app/components/ui/sheet/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { default as Sheet } from "./Sheet.vue"
|
||||
export { default as SheetClose } from "./SheetClose.vue"
|
||||
export { default as SheetContent } from "./SheetContent.vue"
|
||||
export { default as SheetDescription } from "./SheetDescription.vue"
|
||||
export { default as SheetFooter } from "./SheetFooter.vue"
|
||||
export { default as SheetHeader } from "./SheetHeader.vue"
|
||||
export { default as SheetTitle } from "./SheetTitle.vue"
|
||||
export { default as SheetTrigger } from "./SheetTrigger.vue"
|
||||
@@ -1,16 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '~/lib/utils';
|
||||
|
||||
const { isSidebarOpened } = storeToRefs(useUiStore())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="h-full grid md:grid-cols-[auto_1fr] bg-background transition-colors after:absolute after:transition-colors"
|
||||
:class="cn({
|
||||
'max-md:after:bg-transparent': isSidebarOpened,
|
||||
'has-sidebar max-md:after:bg-black/20 md:after:opacity-0 md:after:pointer-events-none': isSidebarOpened
|
||||
})"
|
||||
>
|
||||
<Sidebar />
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import type { ClassValue } from "clsx"
|
||||
import { clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
|
||||
3
app/pages/my.vue
Normal file
3
app/pages/my.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
@@ -1,17 +1,20 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "default",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tsConfigPath": ".nuxt/tsconfig.json",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "assets/main.css",
|
||||
"css": "app/assets/main.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"framework": "nuxt",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
"composables": "@/composables",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export default defineI18nConfig(() => ({
|
||||
save: "Save",
|
||||
delete: "Delete",
|
||||
edit: "Edit",
|
||||
close: "Close"
|
||||
},
|
||||
colors: {
|
||||
label: "Color",
|
||||
@@ -69,13 +70,14 @@ export default defineI18nConfig(() => ({
|
||||
backToHome: "Back to home",
|
||||
sidebarMenu: {
|
||||
profile: "Profile",
|
||||
projects: "Projects",
|
||||
appearance: "Appearance",
|
||||
language: "Language",
|
||||
account: "Account",
|
||||
login: "Log in",
|
||||
logout: "Log out",
|
||||
avatarFallback: "Profile",
|
||||
about: "About application",
|
||||
about: "About",
|
||||
},
|
||||
dark: "Dark",
|
||||
light: "Light",
|
||||
@@ -383,6 +385,7 @@ export default defineI18nConfig(() => ({
|
||||
save: "Sauvegarder",
|
||||
delete: "Supprimer",
|
||||
edit: "Modifier",
|
||||
close: "Fermer"
|
||||
},
|
||||
colors: {
|
||||
label: "Couleur",
|
||||
@@ -420,6 +423,7 @@ export default defineI18nConfig(() => ({
|
||||
backToHome: "Retourner à l'accueil",
|
||||
sidebarMenu: {
|
||||
profile: "Profil",
|
||||
projects: "Projets",
|
||||
appearance: "Apparence",
|
||||
language: "Langue",
|
||||
account: "Compte",
|
||||
|
||||
@@ -36,10 +36,12 @@
|
||||
"nuxt": "^4.0.3",
|
||||
"pinia": "^3.0.3",
|
||||
"radix-vue": "^1.9.17",
|
||||
"reka-ui": "^2.4.1",
|
||||
"shadcn-nuxt": "^2.2.0",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^4.0.15"
|
||||
|
||||
59
pnpm-lock.yaml
generated
59
pnpm-lock.yaml
generated
@@ -77,6 +77,9 @@ importers:
|
||||
radix-vue:
|
||||
specifier: ^1.9.17
|
||||
version: 1.9.17(vue@3.5.18(typescript@5.9.2))
|
||||
reka-ui:
|
||||
specifier: ^2.4.1
|
||||
version: 2.4.1(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2))
|
||||
shadcn-nuxt:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(magicast@0.3.5)
|
||||
@@ -89,6 +92,9 @@ importers:
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@4.1.11)
|
||||
tw-animate-css:
|
||||
specifier: ^1.3.6
|
||||
version: 1.3.6
|
||||
vue:
|
||||
specifier: ^3.5.18
|
||||
version: 3.5.18(typescript@5.9.2)
|
||||
@@ -2715,6 +2721,9 @@ packages:
|
||||
'@vueuse/core@10.11.1':
|
||||
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
|
||||
|
||||
'@vueuse/core@12.8.2':
|
||||
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
|
||||
|
||||
'@vueuse/core@13.6.0':
|
||||
resolution: {integrity: sha512-DJbD5fV86muVmBgS9QQPddVX7d9hWYswzlf4bIyUD2dj8GC46R1uNClZhVAmsdVts4xb2jwp1PbpuiA50Qee1A==}
|
||||
peerDependencies:
|
||||
@@ -2765,6 +2774,9 @@ packages:
|
||||
'@vueuse/metadata@10.11.1':
|
||||
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
|
||||
|
||||
'@vueuse/metadata@12.8.2':
|
||||
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
|
||||
|
||||
'@vueuse/metadata@13.6.0':
|
||||
resolution: {integrity: sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==}
|
||||
|
||||
@@ -2777,6 +2789,9 @@ packages:
|
||||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
'@vueuse/shared@12.8.2':
|
||||
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
|
||||
|
||||
'@vueuse/shared@13.6.0':
|
||||
resolution: {integrity: sha512-pDykCSoS2T3fsQrYqf9SyF0QXWHmcGPQ+qiOVjlYSzlWd9dgppB2bFSM1GgKKkt7uzn0BBMV3IbJsUfHG2+BCg==}
|
||||
peerDependencies:
|
||||
@@ -5944,6 +5959,11 @@ packages:
|
||||
rehype-sort-attributes@5.0.1:
|
||||
resolution: {integrity: sha512-Bxo+AKUIELcnnAZwJDt5zUDDRpt4uzhfz9d0PVGhcxYWsbFj5Cv35xuWxu5r1LeYNFNhgGqsr9Q2QiIOM/Qctg==}
|
||||
|
||||
reka-ui@2.4.1:
|
||||
resolution: {integrity: sha512-NB7DrCsODN8MH02BWtgiExygfFcuuZ5/PTn6fMgjppmFHqePvNhmSn1LEuF35nel6PFbA4v+gdj0IoGN1yZ+vw==}
|
||||
peerDependencies:
|
||||
vue: '>= 3.2.0'
|
||||
|
||||
remark-emoji@5.0.1:
|
||||
resolution: {integrity: sha512-QCqTSvcZ65Ym+P+VyBKd4JfJfh7icMl7cIOGVmPMzWkDtdD8pQ0nQG7yxGolVIiMzSx90EZ7SwNiVpYpfTxn7w==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -6485,6 +6505,9 @@ packages:
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
tw-animate-css@1.3.6:
|
||||
resolution: {integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -9885,6 +9908,15 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/core@12.8.2(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 12.8.2
|
||||
'@vueuse/shared': 12.8.2(typescript@5.9.2)
|
||||
vue: 3.5.18(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/core@13.6.0(vue@3.5.18(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
@@ -9905,6 +9937,8 @@ snapshots:
|
||||
|
||||
'@vueuse/metadata@10.11.1': {}
|
||||
|
||||
'@vueuse/metadata@12.8.2': {}
|
||||
|
||||
'@vueuse/metadata@13.6.0': {}
|
||||
|
||||
'@vueuse/nuxt@13.6.0(magicast@0.3.5)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.2.0)(@vue/compiler-sfc@3.5.18)(better-sqlite3@12.2.0)(db0@0.3.2(better-sqlite3@12.2.0))(eslint@9.32.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.90.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.0(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1))(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
|
||||
@@ -9925,6 +9959,12 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@12.8.2(typescript@5.9.2)':
|
||||
dependencies:
|
||||
vue: 3.5.18(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/shared@13.6.0(vue@3.5.18(typescript@5.9.2))':
|
||||
dependencies:
|
||||
vue: 3.5.18(typescript@5.9.2)
|
||||
@@ -13755,6 +13795,23 @@ snapshots:
|
||||
'@types/hast': 3.0.4
|
||||
unist-util-visit: 5.0.0
|
||||
|
||||
reka-ui@2.4.1(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2)):
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.3
|
||||
'@floating-ui/vue': 1.1.8(vue@3.5.18(typescript@5.9.2))
|
||||
'@internationalized/date': 3.8.2
|
||||
'@internationalized/number': 3.6.4
|
||||
'@tanstack/vue-virtual': 3.13.12(vue@3.5.18(typescript@5.9.2))
|
||||
'@vueuse/core': 12.8.2(typescript@5.9.2)
|
||||
'@vueuse/shared': 12.8.2(typescript@5.9.2)
|
||||
aria-hidden: 1.2.6
|
||||
defu: 6.1.4
|
||||
ohash: 2.0.11
|
||||
vue: 3.5.18(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- typescript
|
||||
|
||||
remark-emoji@5.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -14461,6 +14518,8 @@ snapshots:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
tw-animate-css@1.3.6: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
Reference in New Issue
Block a user