courrier
This commit is contained in:
parent
bf348135ff
commit
0ad05e08ce
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useCallback, useMemo, memo } from "react";
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
@ -50,7 +50,8 @@ const requestNotificationPermission = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MainNav() {
|
// Use React.memo to memoize the entire component
|
||||||
|
export const MainNav = memo(function MainNav() {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const [userStatus, setUserStatus] = useState<'online' | 'busy' | 'away'>('online');
|
const [userStatus, setUserStatus] = useState<'online' | 'busy' | 'away'>('online');
|
||||||
@ -59,11 +60,8 @@ export function MainNav() {
|
|||||||
// Use the unread announcements hook with memo to prevent unnecessary re-renders
|
// Use the unread announcements hook with memo to prevent unnecessary re-renders
|
||||||
const { hasUnread } = useUnreadAnnouncements();
|
const { hasUnread } = useUnreadAnnouncements();
|
||||||
|
|
||||||
console.log("Session:", session);
|
// Updated function to get user initials - memoize this
|
||||||
console.log("Status:", status);
|
const getUserInitials = useCallback(() => {
|
||||||
|
|
||||||
// Updated function to get user initials
|
|
||||||
const getUserInitials = () => {
|
|
||||||
if (session?.user?.name) {
|
if (session?.user?.name) {
|
||||||
// Split the full name and get initials
|
// Split the full name and get initials
|
||||||
const names = session.user.name.split(' ');
|
const names = session.user.name.split(' ');
|
||||||
@ -74,15 +72,15 @@ export function MainNav() {
|
|||||||
return names[0].slice(0, 2).toUpperCase();
|
return names[0].slice(0, 2).toUpperCase();
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
};
|
}, [session?.user?.name]);
|
||||||
|
|
||||||
// Function to get display name
|
// Function to get display name - memoize this
|
||||||
const getDisplayName = () => {
|
const getDisplayName = useCallback(() => {
|
||||||
return session?.user?.name || "User";
|
return session?.user?.name || "User";
|
||||||
};
|
}, [session?.user?.name]);
|
||||||
|
|
||||||
// Function to get user role
|
// Function to get user role - memoize this
|
||||||
const getUserRole = () => {
|
const getUserRole = useCallback(() => {
|
||||||
if (session?.user?.role) {
|
if (session?.user?.role) {
|
||||||
if (Array.isArray(session.user.role)) {
|
if (Array.isArray(session.user.role)) {
|
||||||
// Filter out technical roles and format remaining ones
|
// Filter out technical roles and format remaining ones
|
||||||
@ -108,17 +106,15 @@ export function MainNav() {
|
|||||||
return session.user.role;
|
return session.user.role;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
};
|
}, [session?.user?.role]);
|
||||||
|
|
||||||
// Function to check if user has a specific role
|
// Function to check if user has a specific role - memoize this
|
||||||
const hasRole = (requiredRoles: string[]) => {
|
const hasRole = useCallback((requiredRoles: string[]) => {
|
||||||
if (!session?.user?.role) {
|
if (!session?.user?.role) {
|
||||||
console.log('No user roles found');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRoles = Array.isArray(session.user.role) ? session.user.role : [session.user.role];
|
const userRoles = Array.isArray(session.user.role) ? session.user.role : [session.user.role];
|
||||||
console.log('Raw user roles:', userRoles);
|
|
||||||
|
|
||||||
// Clean up user roles by removing prefixes and converting to lowercase
|
// Clean up user roles by removing prefixes and converting to lowercase
|
||||||
const cleanUserRoles = userRoles.map(role =>
|
const cleanUserRoles = userRoles.map(role =>
|
||||||
@ -126,21 +122,18 @@ export function MainNav() {
|
|||||||
.replace(/^ROLE_/, '') // Remove ROLE_ prefix
|
.replace(/^ROLE_/, '') // Remove ROLE_ prefix
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
);
|
);
|
||||||
console.log('Clean user roles:', cleanUserRoles);
|
|
||||||
|
|
||||||
// Clean required roles
|
// Clean required roles
|
||||||
const cleanRequiredRoles = requiredRoles.map(role => role.toLowerCase());
|
const cleanRequiredRoles = requiredRoles.map(role => role.toLowerCase());
|
||||||
console.log('Clean required roles:', cleanRequiredRoles);
|
|
||||||
|
|
||||||
// Check if user has any of the required roles
|
// Check if user has any of the required roles
|
||||||
const hasAnyRole = cleanRequiredRoles.some(role => cleanUserRoles.includes(role));
|
const hasAnyRole = cleanRequiredRoles.some(role => cleanUserRoles.includes(role));
|
||||||
console.log('Has any role:', hasAnyRole);
|
|
||||||
|
|
||||||
return hasAnyRole;
|
return hasAnyRole;
|
||||||
};
|
}, [session?.user?.role]);
|
||||||
|
|
||||||
// Status configurations
|
// Status configurations
|
||||||
const statusConfig = {
|
const statusConfig = useMemo(() => ({
|
||||||
online: {
|
online: {
|
||||||
color: 'text-green-500',
|
color: 'text-green-500',
|
||||||
label: 'Online',
|
label: 'Online',
|
||||||
@ -156,10 +149,10 @@ export function MainNav() {
|
|||||||
label: 'Away',
|
label: 'Away',
|
||||||
notifications: false
|
notifications: false
|
||||||
},
|
},
|
||||||
};
|
}), []);
|
||||||
|
|
||||||
// Handle status change
|
// Handle status change
|
||||||
const handleStatusChange = async (newStatus: 'online' | 'busy' | 'away') => {
|
const handleStatusChange = useCallback(async (newStatus: 'online' | 'busy' | 'away') => {
|
||||||
setUserStatus(newStatus);
|
setUserStatus(newStatus);
|
||||||
|
|
||||||
if (newStatus !== 'online') {
|
if (newStatus !== 'online') {
|
||||||
@ -181,19 +174,19 @@ export function MainNav() {
|
|||||||
// Re-enable notifications if going back online
|
// Re-enable notifications if going back online
|
||||||
requestNotificationPermission();
|
requestNotificationPermission();
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// Base menu items (available for everyone)
|
// Base menu items (available for everyone)
|
||||||
const baseMenuItems = [
|
const baseMenuItems = useMemo(() => [
|
||||||
{
|
{
|
||||||
title: "QG",
|
title: "QG",
|
||||||
icon: Target,
|
icon: Target,
|
||||||
href: '/qg',
|
href: '/qg',
|
||||||
},
|
},
|
||||||
];
|
], []);
|
||||||
|
|
||||||
// Role-specific menu items
|
// Role-specific menu items
|
||||||
const roleSpecificItems = [
|
const roleSpecificItems = useMemo(() => [
|
||||||
{
|
{
|
||||||
title: "ShowCase",
|
title: "ShowCase",
|
||||||
icon: Lightbulb,
|
icon: Lightbulb,
|
||||||
@ -212,18 +205,64 @@ export function MainNav() {
|
|||||||
href: '/the-message',
|
href: '/the-message',
|
||||||
requiredRoles: ["mediation", "expression"],
|
requiredRoles: ["mediation", "expression"],
|
||||||
},
|
},
|
||||||
];
|
], []);
|
||||||
|
|
||||||
// Get visible menu items based on user roles
|
// Get visible menu items based on user roles
|
||||||
const visibleMenuItems = [
|
const visibleMenuItems = useMemo(() => [
|
||||||
...baseMenuItems,
|
...baseMenuItems,
|
||||||
...roleSpecificItems.filter(item => hasRole(item.requiredRoles))
|
...roleSpecificItems.filter(item => hasRole(item.requiredRoles))
|
||||||
];
|
], [baseMenuItems, roleSpecificItems, hasRole]);
|
||||||
|
|
||||||
// Format current date and time
|
// Format current date and time
|
||||||
const now = new Date();
|
const dateTimeDisplay = useMemo(() => {
|
||||||
const formattedDate = format(now, "d MMMM yyyy", { locale: fr });
|
const now = new Date();
|
||||||
const formattedTime = format(now, "HH:mm");
|
const formattedDate = format(now, "d MMMM yyyy", { locale: fr });
|
||||||
|
const formattedTime = format(now, "HH:mm");
|
||||||
|
return { formattedDate, formattedTime };
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle sidebar toggle
|
||||||
|
const toggleSidebar = useCallback(() => {
|
||||||
|
setIsSidebarOpen(prev => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle notes dialog
|
||||||
|
const toggleNotesDialog = useCallback(() => {
|
||||||
|
setIsNotesDialogOpen(prev => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle logout
|
||||||
|
const handleLogout = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// First sign out from NextAuth
|
||||||
|
await signOut({
|
||||||
|
callbackUrl: '/signin',
|
||||||
|
redirect: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then redirect to Keycloak logout with proper parameters
|
||||||
|
const keycloakLogoutUrl = new URL(
|
||||||
|
`${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add required parameters
|
||||||
|
keycloakLogoutUrl.searchParams.append(
|
||||||
|
'post_logout_redirect_uri',
|
||||||
|
window.location.origin
|
||||||
|
);
|
||||||
|
keycloakLogoutUrl.searchParams.append(
|
||||||
|
'id_token_hint',
|
||||||
|
session?.accessToken || ''
|
||||||
|
);
|
||||||
|
|
||||||
|
// Redirect to Keycloak logout
|
||||||
|
window.location.href = keycloakLogoutUrl.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during logout:', error);
|
||||||
|
// Fallback to simple redirect if something goes wrong
|
||||||
|
window.location.href = '/signin';
|
||||||
|
}
|
||||||
|
}, [session?.accessToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -232,7 +271,7 @@ export function MainNav() {
|
|||||||
{/* Left side */}
|
{/* Left side */}
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
onClick={toggleSidebar}
|
||||||
className="text-white/80 hover:text-white"
|
className="text-white/80 hover:text-white"
|
||||||
>
|
>
|
||||||
<Menu className="w-5 h-5" />
|
<Menu className="w-5 h-5" />
|
||||||
@ -254,7 +293,7 @@ export function MainNav() {
|
|||||||
<span className="sr-only">TimeTracker</span>
|
<span className="sr-only">TimeTracker</span>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsNotesDialogOpen(true)}
|
onClick={toggleNotesDialog}
|
||||||
className='text-white/80 hover:text-white'
|
className='text-white/80 hover:text-white'
|
||||||
>
|
>
|
||||||
<PenLine className='w-5 h-5' />
|
<PenLine className='w-5 h-5' />
|
||||||
@ -289,8 +328,8 @@ export function MainNav() {
|
|||||||
<div className="flex items-center space-x-8">
|
<div className="flex items-center space-x-8">
|
||||||
{/* Date and Time with smaller text */}
|
{/* Date and Time with smaller text */}
|
||||||
<div className="text-white/80 text-sm">
|
<div className="text-white/80 text-sm">
|
||||||
<span className="mr-2">{formattedDate}</span>
|
<span className="mr-2">{dateTimeDisplay.formattedDate}</span>
|
||||||
<span>{formattedTime}</span>
|
<span>{dateTimeDisplay.formattedTime}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NotificationBadge />
|
<NotificationBadge />
|
||||||
@ -352,37 +391,7 @@ export function MainNav() {
|
|||||||
))}
|
))}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
|
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
|
||||||
onClick={async () => {
|
onClick={handleLogout}
|
||||||
try {
|
|
||||||
// First sign out from NextAuth
|
|
||||||
await signOut({
|
|
||||||
callbackUrl: '/signin',
|
|
||||||
redirect: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then redirect to Keycloak logout with proper parameters
|
|
||||||
const keycloakLogoutUrl = new URL(
|
|
||||||
`${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add required parameters
|
|
||||||
keycloakLogoutUrl.searchParams.append(
|
|
||||||
'post_logout_redirect_uri',
|
|
||||||
window.location.origin
|
|
||||||
);
|
|
||||||
keycloakLogoutUrl.searchParams.append(
|
|
||||||
'id_token_hint',
|
|
||||||
session?.accessToken || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
// Redirect to Keycloak logout
|
|
||||||
window.location.href = keycloakLogoutUrl.toString();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error during logout:', error);
|
|
||||||
// Fallback to simple redirect if something goes wrong
|
|
||||||
window.location.href = '/signin';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
<span>Déconnexion</span>
|
<span>Déconnexion</span>
|
||||||
@ -406,4 +415,4 @@ export function MainNav() {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type React from "react";
|
import React, { memo, useCallback, useMemo } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@ -44,7 +44,8 @@ interface MenuItem {
|
|||||||
requiredRole?: string | string[];
|
requiredRole?: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
// Memoize the entire Sidebar component to prevent unnecessary re-renders
|
||||||
|
export const Sidebar = memo(function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@ -59,8 +60,8 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if user has a specific role
|
// Function to check if user has a specific role - memoize this logic
|
||||||
const hasRole = (requiredRole: string | string[] | undefined) => {
|
const hasRole = useCallback((requiredRole: string | string[] | undefined) => {
|
||||||
// If no role is required, allow access
|
// If no role is required, allow access
|
||||||
if (!requiredRole) {
|
if (!requiredRole) {
|
||||||
return true;
|
return true;
|
||||||
@ -107,10 +108,10 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}, [session]);
|
||||||
|
|
||||||
// Base menu items (available for everyone)
|
// Base menu items (available for everyone)
|
||||||
const baseMenuItems: MenuItem[] = [
|
const baseMenuItems: MenuItem[] = useMemo(() => [
|
||||||
{
|
{
|
||||||
title: "Pages",
|
title: "Pages",
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
@ -158,10 +159,10 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
href: "/agilite",
|
href: "/agilite",
|
||||||
iframe: process.env.NEXT_PUBLIC_IFRAME_AGILITY_URL,
|
iframe: process.env.NEXT_PUBLIC_IFRAME_AGILITY_URL,
|
||||||
},
|
},
|
||||||
];
|
], []);
|
||||||
|
|
||||||
// Role-specific menu items
|
// Role-specific menu items
|
||||||
const roleSpecificItems: MenuItem[] = [
|
const roleSpecificItems: MenuItem[] = useMemo(() => [
|
||||||
{
|
{
|
||||||
title: "Artlab",
|
title: "Artlab",
|
||||||
icon: Palette,
|
icon: Palette,
|
||||||
@ -190,25 +191,45 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
iframe: process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL,
|
iframe: process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL,
|
||||||
requiredRole: "mediation",
|
requiredRole: "mediation",
|
||||||
},
|
},
|
||||||
];
|
], []);
|
||||||
|
|
||||||
// Combine base items with role-specific items based on user roles
|
// Combine base items with role-specific items based on user roles
|
||||||
const visibleMenuItems = [
|
const visibleMenuItems = useMemo(() => [
|
||||||
...baseMenuItems,
|
...baseMenuItems,
|
||||||
...roleSpecificItems.filter(item => {
|
...roleSpecificItems.filter(item => hasRole(item.requiredRole))
|
||||||
const isVisible = hasRole(item.requiredRole);
|
], [baseMenuItems, roleSpecificItems, hasRole]);
|
||||||
return isVisible;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleNavigation = (href: string, external?: boolean) => {
|
const handleNavigation = useCallback((href: string, external?: boolean) => {
|
||||||
if (external && href) {
|
if (external && href) {
|
||||||
window.open(href, "_blank");
|
window.open(href, "_blank");
|
||||||
} else {
|
} else {
|
||||||
router.push(href);
|
router.push(href);
|
||||||
}
|
}
|
||||||
onClose();
|
onClose();
|
||||||
};
|
}, [router, onClose]);
|
||||||
|
|
||||||
|
// Memoize the menu render to prevent unnecessary recalculation
|
||||||
|
const renderMenu = useMemo(() => (
|
||||||
|
<div className="px-2 py-2">
|
||||||
|
{visibleMenuItems.map((item, index) => (
|
||||||
|
<div key={`${item.title}-${index}`} className="mb-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-start font-normal",
|
||||||
|
pathname === item.href
|
||||||
|
? "bg-accent text-accent-foreground"
|
||||||
|
: "hover:bg-accent hover:text-accent-foreground"
|
||||||
|
)}
|
||||||
|
onClick={() => handleNavigation(item.href, item.external)}
|
||||||
|
>
|
||||||
|
<item.icon className="mr-2 h-4 w-4" />
|
||||||
|
{item.title}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
), [visibleMenuItems, pathname, handleNavigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -247,25 +268,15 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Menu Items */}
|
{/* Menu Items - Use memoized menu */}
|
||||||
<div className="space-y-1 p-4">
|
{renderMenu}
|
||||||
{visibleMenuItems.map((item) => (
|
|
||||||
<Button
|
{/* Calendar Navigation */}
|
||||||
key={item.title}
|
<div className="px-2 pb-4">
|
||||||
variant="ghost"
|
<CalendarNav />
|
||||||
className={cn(
|
|
||||||
"w-full justify-start gap-2 text-black hover:bg-gray-100",
|
|
||||||
pathname === item.href && !item.external && "bg-gray-100"
|
|
||||||
)}
|
|
||||||
onClick={() => handleNavigation(item.href, item.external)}
|
|
||||||
>
|
|
||||||
<item.icon className="h-5 w-5" />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@ -5,25 +5,8 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef<
|
// Memoize ScrollBar component to prevent unnecessary re-renders
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
const ScrollBar = React.memo(React.forwardRef<
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<ScrollAreaPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={cn("relative overflow-hidden", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
||||||
{children}
|
|
||||||
</ScrollAreaPrimitive.Viewport>
|
|
||||||
<ScrollBar />
|
|
||||||
<ScrollAreaPrimitive.Corner />
|
|
||||||
</ScrollAreaPrimitive.Root>
|
|
||||||
))
|
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|
||||||
|
|
||||||
const ScrollBar = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
@ -42,7 +25,26 @@ const ScrollBar = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
))
|
)))
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||||
|
|
||||||
|
// Memoize ScrollArea component to prevent unnecessary re-renders
|
||||||
|
const ScrollArea = React.memo(React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("relative overflow-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
)))
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
export { ScrollArea, ScrollBar }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user