Files
pos-system/apps/web-client/src/features/shared/components/layout/mobile-layout/mobile-bottom-nav.tsx

149 lines
3.9 KiB
TypeScript

'use client';
import React from 'react';
import { MessageCircle, User, Settings, Home, Search } from 'lucide-react';
import { cn } from '@/shared/lib/utils';
import type { BottomNavItem } from './mobile-layout';
/**
* EN: Native-style Bottom Navigation Component
* VI: Component Bottom Navigation theo phong cách Native
*
* Pre-configured bottom navigation for common app patterns.
* Bottom navigation được cấu hình sẵn cho các pattern app thông dụng.
*
* @example
* ```tsx
* <MobileBottomNav
* activeItem="chat"
* onItemPress={handleNavPress}
* showBadges={{ chat: 3 }}
* />
* ```
*/
export interface MobileBottomNavProps {
/** Active navigation item ID */
activeItem?: string;
/** Callback when item is pressed */
onItemPress?: (itemId: string) => void;
/** Show badges on items */
showBadges?: Record<string, number>;
/** Custom navigation items */
customItems?: BottomNavItem[];
/** Hide labels on inactive items */
hideLabels?: boolean;
}
/**
* Pre-configured bottom navigation items for common apps
*/
const DEFAULT_NAV_ITEMS: BottomNavItem[] = [
{
id: 'home',
label: 'Home',
icon: <Home className="w-6 h-6" />,
},
{
id: 'search',
label: 'Search',
icon: <Search className="w-6 h-6" />,
},
{
id: 'chat',
label: 'Chat',
icon: <MessageCircle className="w-6 h-6" />,
},
{
id: 'profile',
label: 'Profile',
icon: <User className="w-6 h-6" />,
},
{
id: 'settings',
label: 'Settings',
icon: <Settings className="w-6 h-6" />,
},
];
export function MobileBottomNav({
activeItem,
onItemPress,
showBadges = {},
customItems,
hideLabels = false,
}: MobileBottomNavProps) {
const navItems = customItems || DEFAULT_NAV_ITEMS;
return (
<nav className="flex items-center justify-around w-full h-full px-2">
{navItems.map((item) => {
const badge = showBadges[item.id];
const isActive = activeItem === item.id;
return (
<button
key={item.id}
onClick={() => onItemPress?.(item.id)}
className={cn(
'flex flex-col items-center justify-center',
'min-w-[60px] h-12 rounded-lg',
'transition-all duration-200',
'relative group',
'active:scale-95',
isActive
? 'text-white'
: 'text-white/50 hover:text-white/80'
)}
>
<div className={cn(
'relative mb-1 transition-transform',
isActive ? 'scale-110' : 'group-hover:scale-105'
)}>
{item.icon}
{badge && badge > 0 && (
<div className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full min-w-[18px] h-[18px] flex items-center justify-center px-1 font-medium">
{badge > 99 ? '99+' : badge}
</div>
)}
</div>
{(!hideLabels || isActive) && (
<span className={cn(
'text-xs font-medium transition-all',
isActive ? 'opacity-100' : 'opacity-70'
)}>
{item.label}
</span>
)}
{isActive && (
<div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-6 h-0.5 bg-white rounded-full animate-fadeIn" />
)}
</button>
);
})}
</nav>
);
}
/**
* EN: Hook for managing bottom navigation state
* VI: Hook để quản lý trạng thái bottom navigation
*
* @example
* ```tsx
* const { activeItem, setActiveItem, handleNavPress } = useBottomNav('home');
* ```
*/
export function useBottomNav(initialItem: string = 'home') {
const [activeItem, setActiveItem] = React.useState(initialItem);
const handleNavPress = React.useCallback((itemId: string) => {
setActiveItem(itemId);
}, []);
return {
activeItem,
setActiveItem,
handleNavPress,
};
}