- 新增 loading-animation.tsx 组件,从 public/load.html 转换 - 动画特性: SVG路径逐条绘制 + 渐变填充,最少展示1.5秒 - 替换 homepage/layout.tsx, workspace/redirect.tsx, protected-route.tsx 中的全屏加载 - 修复 sidebar-user.tsx 中 Invitations 按钮在缩回状态下的居中问题
116 lines
5.4 KiB
TypeScript
116 lines
5.4 KiB
TypeScript
import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuGroup,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import {useUser} from '@/contexts';
|
|
import {cn} from '@/lib/utils';
|
|
import {UserPlus, Bell} from 'lucide-react';
|
|
import {useNavigate} from 'react-router-dom';
|
|
|
|
const btnClass = 'flex w-full h-9 justify-start items-center rounded-md font-medium hover:bg-muted cursor-pointer bg-transparent border-0 text-left text-sm';
|
|
|
|
export function SidebarUser({collapsed}: { collapsed: boolean }) {
|
|
const {user, logout} = useUser();
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<div className="w-full mb-2">
|
|
<button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
|
|
onClick={() => navigate('/invitations')}>
|
|
<span className={cn('flex h-6 items-center shrink-0 w-6', collapsed && 'justify-center')}>
|
|
<UserPlus className="h-4 w-4"/>
|
|
</span>
|
|
{!collapsed && <span className="text-sm leading-none">Invitations</span>}
|
|
</button>
|
|
|
|
{!collapsed ? (
|
|
<button type="button" className={cn(btnClass, 'px-2')}
|
|
onClick={() => navigate('/notify')}>
|
|
<span className="flex h-6 items-center shrink-0 w-6">
|
|
<Bell className="h-4 w-4"/>
|
|
</span>
|
|
<span className="text-sm leading-none">Notify</span>
|
|
</button>
|
|
) : (
|
|
<button type="button" className={cn(btnClass, 'justify-center px-0')}
|
|
onClick={() => navigate('/notify')}>
|
|
<span className="flex h-6 items-center shrink-0 w-6 justify-center">
|
|
<Bell className="h-4 w-4"/>
|
|
</span>
|
|
</button>
|
|
)}
|
|
|
|
{user && (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<button
|
|
type="button"
|
|
className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
|
|
/>
|
|
}
|
|
>
|
|
<span
|
|
className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}>
|
|
<Avatar className="h-6 w-6">
|
|
<AvatarImage src={user.avatar_url || ''}/>
|
|
<AvatarFallback>
|
|
{user.username?.charAt(0).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</span>
|
|
{!collapsed && <span className="text-sm leading-none">{user.username}</span>}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent side="right" align="start">
|
|
{!collapsed && (
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
|
<DropdownMenuItem onClick={() => navigate(`/user/${user.username}`)}>
|
|
Profile
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => navigate('/settings')}>
|
|
Settings
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator/>
|
|
<DropdownMenuItem className="text-red-600 focus:text-red-600" onClick={() => logout()}>
|
|
Log out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuGroup>
|
|
)}
|
|
{collapsed && (
|
|
<>
|
|
<DropdownMenuItem onClick={() => {
|
|
navigate(`/user/${user.username}`);
|
|
}}>
|
|
<span className="flex h-6 items-center shrink-0">
|
|
Profile
|
|
</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => {
|
|
navigate('/settings');
|
|
}}>
|
|
<span className="flex h-6 items-center shrink-0">
|
|
Settings
|
|
</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator/>
|
|
<DropdownMenuItem className="text-red-600 focus:text-red-600" onClick={() => logout()}>
|
|
<span className="flex h-6 items-center shrink-0">
|
|
Log out
|
|
</span>
|
|
</DropdownMenuItem>
|
|
</>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|