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