operation-dropdown.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useRef, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import { PluginSource } from '../types'
  6. import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
  7. import ActionButton from '@/app/components/base/action-button'
  8. // import Button from '@/app/components/base/button'
  9. import {
  10. PortalToFollowElem,
  11. PortalToFollowElemContent,
  12. PortalToFollowElemTrigger,
  13. } from '@/app/components/base/portal-to-follow-elem'
  14. import cn from '@/utils/classnames'
  15. type Props = {
  16. source: PluginSource
  17. onInfo: () => void
  18. onCheckVersion: () => void
  19. onRemove: () => void
  20. detailUrl: string
  21. }
  22. const OperationDropdown: FC<Props> = ({
  23. source,
  24. detailUrl,
  25. onInfo,
  26. onCheckVersion,
  27. onRemove,
  28. }) => {
  29. const { t } = useTranslation()
  30. const [open, doSetOpen] = useState(false)
  31. const openRef = useRef(open)
  32. const setOpen = useCallback((v: boolean) => {
  33. doSetOpen(v)
  34. openRef.current = v
  35. }, [doSetOpen])
  36. const handleTrigger = useCallback(() => {
  37. setOpen(!openRef.current)
  38. }, [setOpen])
  39. return (
  40. <PortalToFollowElem
  41. open={open}
  42. onOpenChange={setOpen}
  43. placement='bottom-end'
  44. offset={{
  45. mainAxis: -12,
  46. crossAxis: 36,
  47. }}
  48. >
  49. <PortalToFollowElemTrigger onClick={handleTrigger}>
  50. <div>
  51. <ActionButton className={cn(open && 'bg-state-base-hover')}>
  52. <RiMoreFill className='w-4 h-4' />
  53. </ActionButton>
  54. </div>
  55. </PortalToFollowElemTrigger>
  56. <PortalToFollowElemContent className='z-50'>
  57. <div className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
  58. {source === PluginSource.github && (
  59. <div
  60. onClick={() => {
  61. onInfo()
  62. handleTrigger()
  63. }}
  64. className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
  65. >{t('plugin.detailPanel.operation.info')}</div>
  66. )}
  67. {source === PluginSource.github && (
  68. <div
  69. onClick={() => {
  70. onCheckVersion()
  71. handleTrigger()
  72. }}
  73. className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
  74. >{t('plugin.detailPanel.operation.checkUpdate')}</div>
  75. )}
  76. {(source === PluginSource.marketplace || source === PluginSource.github) && (
  77. <a href={detailUrl} target='_blank' className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
  78. <span className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</span>
  79. <RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
  80. </a>
  81. )}
  82. {(source === PluginSource.marketplace || source === PluginSource.github) && (
  83. <div className='my-1 h-px bg-divider-subtle'></div>
  84. )}
  85. <div
  86. onClick={() => {
  87. onRemove()
  88. handleTrigger()
  89. }}
  90. className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:text-text-destructive hover:bg-state-destructive-hover'
  91. >{t('plugin.detailPanel.operation.remove')}</div>
  92. </div>
  93. </PortalToFollowElemContent>
  94. </PortalToFollowElem>
  95. )
  96. }
  97. export default React.memo(OperationDropdown)