index.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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. let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
  49. if (lastId === '-1')
  50. lastId = undefined
  51. const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
  52. onMoreLoaded({ data: conversations, has_more })
  53. }
  54. return { list: [] }
  55. },
  56. {
  57. target: listRef,
  58. isNoMore: () => {
  59. return isNoMore
  60. },
  61. reloadDeps: [isNoMore, controlUpdate],
  62. },
  63. )
  64. return (
  65. <nav
  66. ref={listRef}
  67. className={cn(className, 'shrink-0 space-y-1 bg-white pb-[85px] overflow-y-auto')}
  68. >
  69. {list.map((item) => {
  70. const isCurrent = item.id === currentId
  71. const ItemIcon
  72. = isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
  73. return (
  74. <div
  75. onClick={() => onCurrentIdChange(item.id)}
  76. key={item.id}
  77. className={cn(s.item,
  78. isCurrent
  79. ? 'bg-primary-50 text-primary-600'
  80. : 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
  81. 'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
  82. )}
  83. >
  84. <div className='flex items-center w-0 grow'>
  85. <ItemIcon
  86. className={cn(
  87. isCurrent
  88. ? 'text-primary-600'
  89. : 'text-gray-400 group-hover:text-gray-500',
  90. 'mr-3 h-5 w-5 flex-shrink-0',
  91. )}
  92. aria-hidden="true"
  93. />
  94. <span>{item.name}</span>
  95. </div>
  96. {item.id !== '-1' && (
  97. <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
  98. <ItemOperation
  99. isPinned={isPinned}
  100. togglePin={() => onPinChanged(item.id)}
  101. isShowDelete
  102. onDelete={() => onDelete(item.id)}
  103. />
  104. </div>
  105. )}
  106. </div>
  107. )
  108. })}
  109. </nav>
  110. )
  111. }
  112. export default React.memo(List)