feat: enhance header with notification buttons and user dropdown

- Updated header component to include notification buttons with icons.
- Introduced a new NavUser component for user-related actions in the sidebar.
- Added NotificationDot component for visual notification indicators.
- Created UserCard component to display user information.
- Implemented UserDropdown for user settings and logout functionality.
- Added Avatar component for user images with fallback support.
- Refactored Sheet and Tooltip components for consistency and improved styling.
- Introduced QueryProvider for managing React Query context.
- Updated SidebarProvider to use custom sidebar implementation.
- Enhanced mobile detection hook for better responsiveness.
- Updated dependencies in yarn.lock for new features and fixes.

feat: remove dot
This commit is contained in:
Maximilian Liebmann 2025-06-22 23:22:53 +02:00 committed by SomeCodecat
parent 9225d8435a
commit 1d9ab84047
19 changed files with 501 additions and 104 deletions

View file

@ -27,9 +27,10 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",

View file

@ -33,6 +33,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
--text-muted: var(--neutral-300);
--muted-input: var(--neutral-600);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
@ -157,6 +158,7 @@
--color-text-alt: var(--text-alt);
--color-text-input: var(--text-input);
--color-text-muted-input: var(--text-muted-input);
--color-text-muted: var(--text-muted);
--color-muted-input: var(--muted-input);
--color-background-disabled: var(--neutral-500);
@ -280,6 +282,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
--text-muted: var(--neutral-300);
--muted-input: var(--neutral-500);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);

View file

@ -2,7 +2,7 @@ import { ThemeProvider } from '@/components/wrappers/theme-provider';
import type { Metadata } from 'next';
import './globals.css';
import { QueryProvider } from '@/components/query-provider';
import { QueryProvider } from '@/components/wrappers/query-provider';
export const metadata: Metadata = {
title: 'MeetUp',

View file

@ -0,0 +1,40 @@
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
// DropdownMenuGroup,
// DropdownMenuItem,
// DropdownMenuLabel,
// DropdownMenuPortal,
// DropdownMenuSeparator,
// DropdownMenuShortcut,
// DropdownMenuSub,
// DropdownMenuSubContent,
// DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { NDot, NotificationDot } from '@/components/misc/notification-dot';
export function NotificationButton({
dotVariant,
children,
...props
}: {
dotVariant?: NDot;
children: React.ReactNode;
} & React.ComponentProps<typeof Button>) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type='button' variant='outline_primary' {...props}>
{children}
<NotificationDot
dotVariant={dotVariant}
className='absolute ml-[30px] mt-[30px]'
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'></DropdownMenuContent>
</DropdownMenu>
);
}

View file

@ -6,27 +6,27 @@ import {
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
// SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
// SidebarInput,
// SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
// SidebarMenuAction,
// SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
} from '@/components/ui/sidebar';
// SidebarMenuSkeleton,
// SidebarMenuSub,
// SidebarMenuSubButton,
// SidebarMenuSubItem,
// SidebarProvider,
// SidebarRail,
// SidebarSeparator,
// SidebarTrigger,
// useSidebar,
} from '@/components/custom-ui/sidebar';
import { ChevronDown } from 'lucide-react';
import {
@ -39,8 +39,6 @@ import Logo from '@/components/misc/logo';
import Link from 'next/link';
import { ThemePicker } from '@/components/misc/theme-picker';
import {
Star,
CalendarDays,

View file

@ -466,7 +466,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
<li
data-slot='sidebar-menu-item'
data-sidebar='menu-item'
className={cn('group/menu-item relative', className)}
className={cn('group/menu-item relative list-none', className)}
{...props}
/>
);

View file

@ -1,5 +1,22 @@
import { SidebarTrigger } from '@/components/ui/sidebar';
import { SidebarTrigger } from '@/components/custom-ui/sidebar';
import { ThemePicker } from '@/components/misc/theme-picker';
import { NotificationButton } from '@/components/buttons/notification-button';
import { BellRing, Inbox } from 'lucide-react';
import UserDropdown from '@/components/misc/user-dropdown';
const items = [
{
title: 'Calendar',
url: '#',
icon: Inbox,
},
{
title: 'Friends',
url: '#',
icon: BellRing,
},
];
export default function Header({
children,
@ -8,13 +25,24 @@ export default function Header({
}>) {
return (
<div className='w-full grid grid-rows-[50px_1fr] h-screen'>
<header className='border-b-1 grid-cols-[1fr_3fr_1fr] grid items-center px-2'>
<header className='border-b-1 grid-cols-[1fr_3fr_1fr] grid items-center px-2 shadow-md'>
<span className='flex justify-start'>
<SidebarTrigger variant='outline_primary' size='icon' />
</span>
<span className='flex justify-center'>Search</span>
<span className='flex justify-end'>
<span className='flex gap-1 justify-end'>
<ThemePicker />
{items.map((item) => (
<NotificationButton
key={item.title}
variant='outline_primary'
dotVariant='hidden'
size='icon'
>
<item.icon />
</NotificationButton>
))}
<UserDropdown />
</span>
</header>
<main>{children}</main>

View file

@ -0,0 +1,110 @@
'use client';
import {
BadgeCheck,
Bell,
ChevronsUpDown,
CreditCard,
LogOut,
Sparkles,
} from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@/components/custom-ui/sidebar';
export function NavUser({
user,
}: {
user: {
name: string;
email: string;
avatar: string;
};
}) {
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size='lg'
className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
>
<Avatar className='h-8 w-8 rounded-lg'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>CN</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-left text-sm leading-tight'>
<span className='truncate font-medium'>{user.name}</span>
<span className='truncate text-xs'>{user.email}</span>
</div>
<ChevronsUpDown className='ml-auto size-4' />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className='w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg'
side={isMobile ? 'bottom' : 'right'}
align='end'
sideOffset={4}
>
<DropdownMenuLabel className='p-0 font-normal'>
<div className='flex items-center gap-2 px-1 py-1.5 text-left text-sm'>
<Avatar className='h-8 w-8 rounded-lg'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>CN</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-left text-sm leading-tight'>
<span className='truncate font-medium'>{user.name}</span>
<span className='truncate text-xs'>{user.email}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}

View file

@ -0,0 +1,35 @@
import { cn } from '@/lib/utils';
import { cva, type VariantProps } from 'class-variance-authority';
import { CircleSmall } from 'lucide-react';
const dotVariants = cva('', {
variants: {
variant: {
neutral: 'fill-neutral-900',
active: 'fill-red-600 stroke-red-600',
hidden: 'hidden',
},
},
defaultVariants: {
variant: 'hidden',
},
});
function NotificationDot({
className,
dotVariant,
...props
}: {
className: string;
dotVariant: VariantProps<typeof dotVariants>['variant'];
}) {
return (
<CircleSmall
className={cn(dotVariants({ variant: dotVariant }), className)}
{...props}
/>
);
}
export type NDot = VariantProps<typeof dotVariants>['variant'];
export { NotificationDot, dotVariants };

View file

@ -0,0 +1,29 @@
import { useGetApiUserMe } from '@/generated/api/user/user';
import { Avatar } from '@/components/ui/avatar';
import Image from 'next/image';
import { User } from 'lucide-react';
export default function UserCard() {
const { data } = useGetApiUserMe();
return (
<div className='w-full'>
<Avatar className='flex justify-center items-center'>
{data?.data.user.image ? (
<Image
className='border-2 rounded-md'
src={data?.data.user.image}
alt='Avatar'
width='50'
height='50'
/>
) : (
<User />
)}
</Avatar>
<div className='flex justify-center'>{data?.data.user.name}</div>
<div className='flex justify-center text-text-muted'>
{data?.data.user.email}
</div>
</div>
);
}

View file

@ -0,0 +1,59 @@
'use client';
import { Avatar } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
// DropdownMenuLabel,
// DropdownMenuPortal,
DropdownMenuSeparator,
// DropdownMenuShortcut,
// DropdownMenuSub,
// DropdownMenuSubContent,
// DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useGetApiUserMe } from '@/generated/api/user/user';
import { ChevronDown, User } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import UserCard from '@/components/misc/user-card';
export default function UserDropdown() {
const { data } = useGetApiUserMe();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline_primary'>
<Avatar className='flex justify-center items-center'>
{data?.data.user.image ? (
<Image
src={data?.data.user.image}
alt='Avatar'
width='20'
height='20'
/>
) : (
<User />
)}
</Avatar>
<ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem>
<UserCard />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Link href='/logout'>Logout</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View file

@ -0,0 +1,53 @@
'use client';
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '@/lib/utils';
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot='avatar'
className={cn(
'relative flex shrink-0 overflow-hidden rounded-md',
className,
)}
{...props}
/>
);
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot='avatar-image'
className={cn('aspect-square size-full', className)}
{...props}
/>
);
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot='avatar-fallback'
className={cn(
'bg-muted flex size-full items-center justify-center rounded-full',
className,
)}
{...props}
/>
);
}
export { Avatar, AvatarImage, AvatarFallback };

View file

@ -1,31 +1,31 @@
"use client"
'use client';
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { XIcon } from 'lucide-react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
return <SheetPrimitive.Root data-slot='sheet' {...props} />;
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
return <SheetPrimitive.Trigger data-slot='sheet-trigger' {...props} />;
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
return <SheetPrimitive.Close data-slot='sheet-close' {...props} />;
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
return <SheetPrimitive.Portal data-slot='sheet-portal' {...props} />;
}
function SheetOverlay({
@ -34,71 +34,71 @@ function SheetOverlay({
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
data-slot='sheet-overlay'
className={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/50",
className
'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/50',
className,
)}
{...props}
/>
)
);
}
function SheetContent({
className,
children,
side = "right",
side = 'right',
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
side?: 'top' | 'right' | 'bottom' | 'left';
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
data-slot='sheet-content'
className={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 w-3/4 border-l sm: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-3/4 border-r sm: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",
className
'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 w-3/4 border-l sm: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-3/4 border-r sm: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',
className,
)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
<SheetPrimitive.Close className='ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none'>
<XIcon className='size-4' />
<span className='sr-only'>Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
data-slot='sheet-header'
className={cn('flex flex-col gap-1.5 p-4', className)}
{...props}
/>
)
);
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
data-slot='sheet-footer'
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
)
);
}
function SheetTitle({
@ -107,11 +107,11 @@ function SheetTitle({
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
data-slot='sheet-title'
className={cn('text-foreground font-semibold', className)}
{...props}
/>
)
);
}
function SheetDescription({
@ -120,11 +120,11 @@ function SheetDescription({
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
data-slot='sheet-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
);
}
export {
@ -136,4 +136,4 @@ export {
SheetFooter,
SheetTitle,
SheetDescription,
}
};

View file

@ -1,13 +1,13 @@
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="skeleton"
className={cn("bg-accent animate-pulse rounded-md", className)}
data-slot='skeleton'
className={cn('bg-accent animate-pulse rounded-md', className)}
{...props}
/>
)
);
}
export { Skeleton }
export { Skeleton };

View file

@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function TooltipProvider({
delayDuration = 0,
@ -11,11 +11,11 @@ function TooltipProvider({
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
data-slot='tooltip-provider'
delayDuration={delayDuration}
{...props}
/>
)
);
}
function Tooltip({
@ -23,15 +23,15 @@ function Tooltip({
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
<TooltipPrimitive.Root data-slot='tooltip' {...props} />
</TooltipProvider>
)
);
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
return <TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />;
}
function TooltipContent({
@ -43,19 +43,19 @@ function TooltipContent({
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
data-slot='tooltip-content'
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className
'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
<TooltipPrimitive.Arrow className='bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]' />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
);
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View file

@ -1,7 +1,7 @@
'use client';
import React from 'react';
import { SidebarProvider } from '../ui/sidebar';
import { SidebarProvider } from '../custom-ui/sidebar';
export default function SidebarProviderWrapper({
defaultOpen,

View file

@ -1,19 +1,21 @@
import * as React from "react"
import * as React from 'react';
const MOBILE_BREAKPOINT = 768
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener('change', onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener('change', onChange);
}, []);
return !!isMobile
return !!isMobile;
}

View file

@ -1299,6 +1299,29 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-avatar@npm:^1.1.10":
version: 1.1.10
resolution: "@radix-ui/react-avatar@npm:1.1.10"
dependencies:
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-primitive": "npm:2.1.3"
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
"@radix-ui/react-use-is-hydrated": "npm:0.1.0"
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/9fb0cf9a9d0fdbeaa2efda476402fc09db2e6ff9cd9aa3ea1d315d9c9579840722a4833725cb196c455e0bd775dfe04221a4f6855685ce89d2133c42e2b07e5f
languageName: node
linkType: hard
"@radix-ui/react-collapsible@npm:^1.1.11":
version: 1.1.11
resolution: "@radix-ui/react-collapsible@npm:1.1.11"
@ -1441,7 +1464,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-dropdown-menu@npm:^2.1.14":
"@radix-ui/react-dropdown-menu@npm:^2.1.15":
version: 2.1.15
resolution: "@radix-ui/react-dropdown-menu@npm:2.1.15"
dependencies:
@ -1951,6 +1974,21 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-use-is-hydrated@npm:0.1.0":
version: 0.1.0
resolution: "@radix-ui/react-use-is-hydrated@npm:0.1.0"
dependencies:
use-sync-external-store: "npm:^1.5.0"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/635079bafe32829fc7405895154568ea94a22689b170489fd6d77668e4885e72ff71ed6d0ea3d602852841ef0f1927aa400fee2178d5dfbeb8bc9297da7d6498
languageName: node
linkType: hard
"@radix-ui/react-use-layout-effect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
@ -6637,9 +6675,10 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.2"
"@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
"@radix-ui/react-avatar": "npm:^1.1.10"
"@radix-ui/react-collapsible": "npm:^1.1.11"
"@radix-ui/react-dialog": "npm:^1.1.14"
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
"@radix-ui/react-dropdown-menu": "npm:^2.1.15"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
@ -9394,7 +9433,7 @@ __metadata:
languageName: node
linkType: hard
"use-sync-external-store@npm:^1.4.0":
"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0":
version: 1.5.0
resolution: "use-sync-external-store@npm:1.5.0"
peerDependencies: