index.tsx 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. 'use client'
  2. import React from 'react'
  3. import { RiVerifiedBadgeLine } from '@remixicon/react'
  4. import type { Plugin } from '../types'
  5. import Icon from '../card/base/card-icon'
  6. import CornerMark from './base/corner-mark'
  7. import Title from './base/title'
  8. import OrgInfo from './base/org-info'
  9. import Description from './base/description'
  10. import Placeholder from './base/placeholder'
  11. import cn from '@/utils/classnames'
  12. import { useGetLanguage } from '@/context/i18n'
  13. import { getLanguage } from '@/i18n/language'
  14. import { useSingleCategories } from '../hooks'
  15. import { renderI18nObject } from '@/hooks/use-i18n'
  16. import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
  17. export type Props = {
  18. className?: string
  19. payload: Plugin
  20. titleLeft?: React.ReactNode
  21. installed?: boolean
  22. installFailed?: boolean
  23. hideCornerMark?: boolean
  24. descriptionLineRows?: number
  25. footer?: React.ReactNode
  26. isLoading?: boolean
  27. loadingFileName?: string
  28. locale?: string
  29. }
  30. const Card = ({
  31. className,
  32. payload,
  33. titleLeft,
  34. installed,
  35. installFailed,
  36. hideCornerMark,
  37. descriptionLineRows = 2,
  38. footer,
  39. isLoading = false,
  40. loadingFileName,
  41. locale: localeFromProps,
  42. }: Props) => {
  43. const defaultLocale = useGetLanguage()
  44. const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
  45. const { t } = useMixedTranslation(localeFromProps)
  46. const { categoriesMap } = useSingleCategories(t)
  47. const { category, type, name, org, label, brief, icon, verified } = payload
  48. const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent-strategy'].includes(type)
  49. const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label
  50. const getLocalizedText = (obj: Record<string, string> | undefined) =>
  51. obj ? renderI18nObject(obj, locale) : ''
  52. const wrapClassName = cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)
  53. if (isLoading) {
  54. return (
  55. <Placeholder
  56. wrapClassName={wrapClassName}
  57. loadingFileName={loadingFileName!}
  58. />
  59. )
  60. }
  61. return (
  62. <div className={wrapClassName}>
  63. {!hideCornerMark && <CornerMark text={cornerMark} />}
  64. {/* Header */}
  65. <div className="flex">
  66. <Icon src={icon} installed={installed} installFailed={installFailed} />
  67. <div className="ml-3 w-0 grow">
  68. <div className="flex items-center h-5">
  69. <Title title={getLocalizedText(label)} />
  70. {verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />}
  71. {titleLeft} {/* This can be version badge */}
  72. </div>
  73. <OrgInfo
  74. className="mt-0.5"
  75. orgName={org}
  76. packageName={name}
  77. />
  78. </div>
  79. </div>
  80. <Description
  81. className="mt-3"
  82. text={getLocalizedText(brief)}
  83. descriptionLineRows={descriptionLineRows}
  84. />
  85. {footer && <div>{footer}</div>}
  86. </div>
  87. )
  88. }
  89. export default React.memo(Card)