import { ChevronDown, Hash, Volume2 } from "lucide-react"; import React from "react"; import { NavLink } from "react-router"; import { useShallow } from "zustand/react/shallow"; import { useFetchUsers } from "~/hooks/use-fetch-user"; import { deleteChannel } from "~/lib/api/client/server"; import { ChannelType, type ServerChannel } from "~/lib/api/types"; import { cn } from "~/lib/utils"; import { useChannelsVoiceStateStore } from "~/stores/channels-voice-state"; import { useGatewayStore } from "~/stores/gateway-store"; import { useUsersStore } from "~/stores/users-store"; import { useVoiceStateStore } from "~/stores/voice-state-store"; import { Button } from "../ui/button"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "../ui/context-menu"; import UserAvatar from "../user-avatar"; import UserContextMenu from "../user-context-menu"; interface ChannelListItemProps { channel: ServerChannel; } const onDeleteChannel = async (channel: ServerChannel) => { await deleteChannel(channel.serverId, channel.id); }; interface ChannelItemWrapperProps { channel: ServerChannel; icon: React.ReactNode; onButtonClick?: () => void; // For direct button clicks (e.g., join voice) navTo?: string; // If provided, the button becomes a NavLink isExplicitlyActive?: boolean; // To set active state externally (e.g., current voice channel) } function ChannelItemWrapper({ channel, icon, onButtonClick, navTo, isExplicitlyActive }: ChannelItemWrapperProps) { const buttonInnerContent = (
{icon}
{channel.name}
); const renderButton = (isNavLinkActive?: boolean) => { // Active state priority: explicit prop > NavLink state > default (false) const isActive = isExplicitlyActive ?? isNavLinkActive ?? false; return ( ); }; let triggerContent: React.ReactNode; if (navTo) { triggerContent = ( {({ isActive: navLinkIsActive }) => renderButton(navLinkIsActive)} ); } else { // For non-NavLink cases (like voice channel), active state is determined by isExplicitlyActive triggerContent = renderButton(); } return ( {triggerContent} onDeleteChannel(channel)}> Delete ); } function ServerCategory({ channel }: ChannelListItemProps) { return (
{channel.name}
); } function ServerVoice({ channel }: ChannelListItemProps) { const updateVoiceState = useGatewayStore((state) => state.updateVoiceState); const voiceStateChannel = useVoiceStateStore((state) => state.activeChannel); const channelVoiceState = useChannelsVoiceStateStore((state) => state.channels[channel.id]) || {}; const userIds = Object.keys(channelVoiceState.users ?? {}); useFetchUsers(userIds); const users = useUsersStore(useShallow((state) => state.users)); const channelUsers = React.useMemo(() => userIds.map((userId) => users[userId]).filter(Boolean), [userIds, users]); const isCurrentVoiceChannel = voiceStateChannel?.serverId === channel.serverId && voiceStateChannel.channelId === channel.id; const handleJoinVoiceChannel = () => { if (isCurrentVoiceChannel) return; updateVoiceState(channel.serverId, channel.id); }; return ( <> } onButtonClick={handleJoinVoiceChannel} isExplicitlyActive={isCurrentVoiceChannel} /> {channelUsers.length > 0 && (
{" "} {/* Added py-1 for spacing */} {channelUsers.map((user) => (
{" "} {/* Added padding and hover for better UX */} {user.displayName || user.username}
))}
)} ); } function ServerText({ channel }: ChannelListItemProps) { return ( } navTo={`/app/server/${channel.serverId}/${channel.id}`} /> ); } export default function ServerChannelListItem({ channel }: ChannelListItemProps) { switch (channel.type) { case ChannelType.SERVER_CATEGORY: return ; case ChannelType.SERVER_VOICE: return ; case ChannelType.SERVER_TEXT: return ; default: return null; } }