ChannelList.tsx raw

   1  import { useChat } from '@/providers/ChatProvider'
   2  import { useSecondaryPage } from '@/PageManager'
   3  import { toChatChannel } from '@/lib/link'
   4  import { cn } from '@/lib/utils'
   5  import { Hash, Plus, Loader2, RefreshCw, Lock, BellOff } from 'lucide-react'
   6  import { useState } from 'react'
   7  import { Button } from '../ui/button'
   8  import CreateChannelDialog from './CreateChannelDialog'
   9  
  10  export default function ChannelList() {
  11    const {
  12      channels,
  13      currentChannel,
  14      isLoadingChannels,
  15      refreshChannels,
  16      unreadCounts,
  17      mutedChannels
  18    } = useChat()
  19    const { push, pop } = useSecondaryPage()
  20    const [showCreate, setShowCreate] = useState(false)
  21  
  22    return (
  23      <div className="flex flex-col h-full">
  24        <div className="flex items-center justify-between px-3 py-2 border-b">
  25          <span className="text-sm font-semibold">Channels</span>
  26          <div className="flex gap-1">
  27            <Button
  28              variant="ghost"
  29              size="icon"
  30              className="size-7"
  31              onClick={() => refreshChannels()}
  32              title="Refresh"
  33            >
  34              <RefreshCw className="size-3.5" />
  35            </Button>
  36            <Button
  37              variant="ghost"
  38              size="icon"
  39              className="size-7"
  40              onClick={() => setShowCreate(true)}
  41              title="Create channel"
  42            >
  43              <Plus className="size-3.5" />
  44            </Button>
  45          </div>
  46        </div>
  47  
  48        <div className="flex-1 overflow-y-auto">
  49          {isLoadingChannels ? (
  50            <div className="flex justify-center py-8">
  51              <Loader2 className="size-5 animate-spin text-muted-foreground" />
  52            </div>
  53          ) : channels.length === 0 ? (
  54            <div className="px-3 py-8 text-center text-sm text-muted-foreground">
  55              No channels yet
  56            </div>
  57          ) : (
  58            channels.map((ch) => {
  59              const unread = unreadCounts[ch.id] || 0
  60              const isMuted = mutedChannels.has(ch.id)
  61              return (
  62                <button
  63                  key={ch.id}
  64                  onClick={() => {
  65                    if (currentChannel && currentChannel.id !== ch.id) {
  66                      pop()
  67                    }
  68                    push(toChatChannel(ch.id))
  69                  }}
  70                  className={cn(
  71                    'flex items-center gap-2 w-full px-3 py-2 text-left text-sm transition-colors hover:bg-accent',
  72                    currentChannel?.id === ch.id && 'bg-accent text-accent-foreground'
  73                  )}
  74                >
  75                  <Hash className="size-4 flex-shrink-0 text-muted-foreground" />
  76                  <div className="min-w-0 flex-1">
  77                    <div className="flex items-center gap-1">
  78                      <span className={cn('truncate font-medium', unread > 0 && !isMuted && 'font-bold')}>
  79                        {ch.name}
  80                      </span>
  81                      {ch.accessMode !== 'open' && <Lock className="size-3 text-muted-foreground flex-shrink-0" />}
  82                      {isMuted && <BellOff className="size-3 text-muted-foreground flex-shrink-0" />}
  83                    </div>
  84                    {ch.about && (
  85                      <div className="truncate text-xs text-muted-foreground">{ch.about}</div>
  86                    )}
  87                  </div>
  88                  {unread > 0 && !isMuted && (
  89                    <span className="inline-flex items-center justify-center size-5 text-xs rounded-full bg-primary text-primary-foreground flex-shrink-0">
  90                      {unread > 99 ? '99+' : unread}
  91                    </span>
  92                  )}
  93                </button>
  94              )
  95            })
  96          )}
  97        </div>
  98  
  99        <CreateChannelDialog open={showCreate} onOpenChange={setShowCreate} />
 100      </div>
 101    )
 102  }
 103