feat: add mobile drawer component for user button menu

This commit is contained in:
javayhu 2025-02-23 15:50:58 +08:00
parent 161cd45fa1
commit fe1bf59d6f
4 changed files with 222 additions and 77 deletions

View File

@ -57,6 +57,7 @@
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"unist-util-visit": "^5.0.0",
"vaul": "^1.1.2",
"zod": "^3.24.2"
},
"devDependencies": {

18
pnpm-lock.yaml generated
View File

@ -152,6 +152,9 @@ importers:
unist-util-visit:
specifier: ^5.0.0
version: 5.0.0
vaul:
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
zod:
specifier: ^3.24.2
version: 3.24.2
@ -3581,6 +3584,12 @@ packages:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
vaul@1.1.2:
resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@ -7203,6 +7212,15 @@ snapshots:
uuid@9.0.1: {}
vaul@1.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3

View File

@ -17,7 +17,15 @@ import { LogOutIcon } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
// import { Drawer } from "vaul";
import {
Drawer,
DrawerTrigger,
DrawerContent,
DrawerOverlay,
DrawerPortal,
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
export function UserButton() {
const router = useRouter();
@ -43,83 +51,83 @@ export function UserButton() {
// }
// Mobile View, use Drawer
// if (isMobile) {
// return (
// <Drawer.Root open={open} onClose={closeDrawer}>
// <Drawer.Trigger onClick={() => setOpen(true)}>
// <UserAvatar
// name={user.name || null}
// image={user.image || null}
// className="size-8 border"
// />
// </Drawer.Trigger>
// <Drawer.Portal>
// <Drawer.Overlay
// className="fixed inset-0 z-40 h-full bg-background/80 backdrop-blur-sm"
// onClick={closeDrawer}
// />
// <Drawer.Content className="fixed inset-x-0 bottom-0 z-50 mt-24 overflow-hidden rounded-t-[10px] border bg-background px-3 text-sm">
// <div className="sticky top-0 z-20 flex w-full items-center justify-center bg-inherit">
// <div className="my-3 h-1.5 w-16 rounded-full bg-muted-foreground/20" />
// </div>
if (isMobile) {
return (
<Drawer open={open} onClose={closeDrawer}>
<DrawerTrigger onClick={() => setOpen(true)}>
<UserAvatar
name={user.name || undefined}
image={user.image || undefined}
className="size-8 border"
/>
</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay className="fixed inset-0 z-40 bg-background/50" />
<DrawerContent className="fixed inset-x-0 bottom-0 z-50 mt-24 overflow-hidden rounded-t-[10px] border bg-background px-3 text-sm">
<DrawerHeader>
<DrawerTitle></DrawerTitle>
</DrawerHeader>
<div className="flex items-center justify-start gap-4 p-2">
<UserAvatar
name={user.name || undefined}
image={user.image || undefined}
className="size-8 border"
/>
<div className="flex flex-col">
{user.name && <p className="font-medium">{user.name}</p>}
{user.email && (
<p className="w-[200px] truncate text-muted-foreground">
{user?.email}
</p>
)}
</div>
</div>
// <div className="flex items-center justify-start gap-2 p-2">
// <div className="flex flex-col">
// {user.name && <p className="font-medium">{user.name}</p>}
// {user.email && (
// <p className="w-[200px] truncate text-muted-foreground">
// {user?.email}
// </p>
// )}
// </div>
// </div>
// <ul className="mb-14 mt-1 w-full text-muted-foreground">
// {userButtonConfig.menus.map((item) => {
// const Icon = Icons[item.icon || "arrowRight"];
// return (
// <li
// key={item.href}
// className="rounded-lg text-foreground hover:bg-muted"
// >
// <Link
// href={item.href}
// onClick={closeDrawer}
// className="flex w-full items-center gap-3 px-2.5 py-2"
// >
// <Icon className="size-4" />
// <p className="text-sm">{item.title}</p>
// </Link>
// </li>
// );
// })}
// <li
// key="logout"
// className="rounded-lg text-foreground hover:bg-muted"
// >
// <Link
// href="#"
// onClick={(event) => {
// event.preventDefault();
// closeDrawer();
// // signOut({
// // callbackUrl: `${window.location.origin}/`,
// // redirect: true,
// // });
// }}
// className="flex w-full items-center gap-3 px-2.5 py-2"
// >
// <LogOutIcon className="size-4" />
// <p className="text-sm">Log out</p>
// </Link>
// </li>
// </ul>
// </Drawer.Content>
// <Drawer.Overlay />
// </Drawer.Portal>
// </Drawer.Root>
// );
// }
<ul className="mb-14 mt-1 w-full text-muted-foreground">
{userButtonConfig.menus.map((item) => {
const Icon = Icons[item.icon || "arrowRight"];
return (
<li
key={item.href}
className="rounded-lg text-foreground hover:bg-muted"
>
<Link
href={item.href}
onClick={closeDrawer}
className="flex w-full items-center gap-3 px-2.5 py-2"
>
<Icon className="size-4" />
<p className="text-sm">{item.title}</p>
</Link>
</li>
);
})}
<li
key="logout"
className="rounded-lg text-foreground hover:bg-muted"
>
<Link
href="#"
onClick={(event) => {
event.preventDefault();
closeDrawer();
// signOut({
// callbackUrl: `${window.location.origin}/`,
// redirect: true,
// });
}}
className="flex w-full items-center gap-3 px-2.5 py-2"
>
<LogOutIcon className="size-4" />
<p className="text-sm">Log out</p>
</Link>
</li>
</ul>
</DrawerContent>
</DrawerPortal>
</Drawer>
);
}
// Desktop View, use DropdownMenu
return (

View File

@ -0,0 +1,118 @@
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger
const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
))
DrawerContent.displayName = "DrawerContent"
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
)
DrawerHeader.displayName = "DrawerHeader"
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
DrawerFooter.displayName = "DrawerFooter"
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}