index.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useRef } from 'react'
  4. import {
  5. ChatBubbleOvalLeftEllipsisIcon,
  6. } from '@heroicons/react/24/outline'
  7. import { useInfiniteScroll } from 'ahooks'
  8. import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
  9. import cn from 'classnames'
  10. import s from './style.module.css'
  11. import type { ConversationItem } from '@/models/share'
  12. import { fetchConversations } from '@/service/share'
  13. import ItemOperation from '@/app/components/explore/item-operation'
  14. export type IListProps = {
  15. className: string
  16. currentId: string
  17. onCurrentIdChange: (id: string) => void
  18. list: ConversationItem[]
  19. isInstalledApp: boolean
  20. installedAppId?: string
  21. onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
  22. isNoMore: boolean
  23. isPinned: boolean
  24. onPinChanged: (id: string) => void
  25. controlUpdate: number
  26. onDelete: (id: string) => void
  27. }
  28. const List: FC<IListProps> = ({
  29. className,
  30. currentId,
  31. onCurrentIdChange,
  32. list,
  33. isInstalledApp,
  34. installedAppId,
  35. onMoreLoaded,
  36. isNoMore,
  37. isPinned,
  38. onPinChanged,
  39. controlUpdate,
  40. onDelete,
  41. }) => {
  42. const listRef = useRef<HTMLDivElement>(null)
  43. useInfiniteScroll(
  44. async () => {
  45. if (!isNoMore) {
  46. const lastId = list[list.length - 1]?.id
  47. const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
  48. onMoreLoaded({ data: conversations, has_more })
  49. }
  50. return { list: [] }
  51. },
  52. {
  53. target: listRef,
  54. isNoMore: () => {
  55. return isNoMore
  56. },
  57. reloadDeps: [isNoMore, controlUpdate],
  58. },
  59. )
  60. return (
  61. <nav
  62. ref={listRef}
  63. className={cn(className, 'shrink-0 space-y-1 bg-white pb-[60px] overflow-y-auto')}
  64. >
  65. {list.map((item) => {
  66. const isCurrent = item.id === currentId
  67. const ItemIcon
  68. = isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
  69. return (
  70. <div
  71. onClick={() => onCurrentIdChange(item.id)}
  72. key={item.id}
  73. className={cn(s.item,
  74. isCurrent
  75. ? 'bg-primary-50 text-primary-600'
  76. : 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
  77. 'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
  78. )}
  79. >
  80. <div className='flex items-center w-0 grow'>
  81. <ItemIcon
  82. className={cn(
  83. isCurrent
  84. ? 'text-primary-600'
  85. : 'text-gray-400 group-hover:text-gray-500',
  86. 'mr-3 h-5 w-5 flex-shrink-0',
  87. )}
  88. aria-hidden="true"
  89. />
  90. <span>{item.name}</span>
  91. </div>
  92. {
  93. !isCurrent && (
  94. <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
  95. <ItemOperation
  96. isPinned={isPinned}
  97. togglePin={() => onPinChanged(item.id)}
  98. isShowDelete
  99. onDelete={() => onDelete(item.id)}
  100. />
  101. </div>
  102. )
  103. }
  104. </div>
  105. )
  106. })}
  107. </nav>
  108. )
  109. }
  110. export default React.memo(List)