Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

menu-dropdown.tsx 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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 type { Placement } from '@floating-ui/react'
  6. import {
  7. RiEqualizer2Line,
  8. } from '@remixicon/react'
  9. import { useRouter } from 'next/navigation'
  10. import Divider from '../../base/divider'
  11. import { removeAccessToken } from '../utils'
  12. import InfoModal from './info-modal'
  13. import ActionButton from '@/app/components/base/action-button'
  14. import {
  15. PortalToFollowElem,
  16. PortalToFollowElemContent,
  17. PortalToFollowElemTrigger,
  18. } from '@/app/components/base/portal-to-follow-elem'
  19. import ThemeSwitcher from '@/app/components/base/theme-switcher'
  20. import type { SiteInfo } from '@/models/share'
  21. import cn from '@/utils/classnames'
  22. type Props = {
  23. data?: SiteInfo
  24. placement?: Placement
  25. hideLogout?: boolean
  26. }
  27. const MenuDropdown: FC<Props> = ({
  28. data,
  29. placement,
  30. hideLogout,
  31. }) => {
  32. const router = useRouter()
  33. const { t } = useTranslation()
  34. const [open, doSetOpen] = useState(false)
  35. const openRef = useRef(open)
  36. const setOpen = useCallback((v: boolean) => {
  37. doSetOpen(v)
  38. openRef.current = v
  39. }, [doSetOpen])
  40. const handleTrigger = useCallback(() => {
  41. setOpen(!openRef.current)
  42. }, [setOpen])
  43. const handleLogout = useCallback(() => {
  44. removeAccessToken()
  45. router.replace(`/webapp-signin?redirect_url=${window.location.href}`)
  46. }, [router])
  47. const [show, setShow] = useState(false)
  48. return (
  49. <>
  50. <PortalToFollowElem
  51. open={open}
  52. onOpenChange={setOpen}
  53. placement={placement || 'bottom-end'}
  54. offset={{
  55. mainAxis: 4,
  56. crossAxis: -4,
  57. }}
  58. >
  59. <PortalToFollowElemTrigger onClick={handleTrigger}>
  60. <div>
  61. <ActionButton size='l' className={cn(open && 'bg-state-base-hover')}>
  62. <RiEqualizer2Line className='h-[18px] w-[18px]' />
  63. </ActionButton>
  64. </div>
  65. </PortalToFollowElemTrigger>
  66. <PortalToFollowElemContent className='z-50'>
  67. <div className='w-[224px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
  68. <div className='p-1'>
  69. <div className={cn('system-md-regular flex cursor-pointer items-center rounded-lg py-1.5 pl-3 pr-2 text-text-secondary')}>
  70. <div className='grow'>{t('common.theme.theme')}</div>
  71. <ThemeSwitcher />
  72. </div>
  73. </div>
  74. <Divider type='horizontal' className='my-0' />
  75. <div className='p-1'>
  76. {data?.privacy_policy && (
  77. <a href={data.privacy_policy} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
  78. <span className='grow'>{t('share.chat.privacyPolicyMiddle')}</span>
  79. </a>
  80. )}
  81. <div
  82. onClick={() => {
  83. handleTrigger()
  84. setShow(true)
  85. }}
  86. className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
  87. >{t('common.userProfile.about')}</div>
  88. </div>
  89. </div>
  90. </PortalToFollowElemContent>
  91. </PortalToFollowElem>
  92. {show && (
  93. <InfoModal
  94. isShow={show}
  95. onClose={() => {
  96. setShow(false)
  97. }}
  98. data={data}
  99. />
  100. )}
  101. </>
  102. )
  103. }
  104. export default React.memo(MenuDropdown)