gpt-card.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { Popover, Tag, TagProps, Tooltip } from 'antd';
  2. import classNames from 'classnames';
  3. import Image from 'next/image';
  4. import { HtmlHTMLAttributes, PropsWithChildren, ReactNode, memo, useMemo } from 'react';
  5. interface Props {
  6. title: string;
  7. desc?: string;
  8. disabled?: boolean;
  9. tags?: (
  10. | string
  11. | {
  12. text: ReactNode;
  13. /** @default false */
  14. border?: boolean;
  15. /** @default default */
  16. color?: TagProps['color'];
  17. }
  18. )[];
  19. operations?: {
  20. children: ReactNode;
  21. label?: string;
  22. onClick?: () => void;
  23. }[];
  24. icon?: ReactNode;
  25. iconBorder?: boolean;
  26. onClick?: () => void;
  27. extraContent?: ReactNode;
  28. }
  29. function GPTCard({
  30. icon,
  31. iconBorder = true,
  32. title,
  33. desc,
  34. tags,
  35. children,
  36. disabled,
  37. operations,
  38. className,
  39. extraContent,
  40. ...props
  41. }: PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & Props>) {
  42. const iconNode = useMemo(() => {
  43. if (!icon) return null;
  44. if (typeof icon === 'string') {
  45. return (
  46. <Image
  47. className={classNames('w-11 h-11 rounded-full mr-4 object-contain bg-white', {
  48. 'border border-gray-200': iconBorder,
  49. })}
  50. width={48}
  51. height={48}
  52. src={icon}
  53. alt={title}
  54. />
  55. );
  56. }
  57. return icon;
  58. }, [icon]);
  59. // TODO: 算子资源标签
  60. const tagNode = useMemo(() => {
  61. if (!tags || !tags.length) return null;
  62. return (
  63. <div className='flex items-center mt-1 flex-wrap'>
  64. {tags.map((tag, index) => {
  65. if (typeof tag === 'string') {
  66. return (
  67. <Tag key={index} className='text-xs' bordered={false} color='default'>
  68. {tag}
  69. </Tag>
  70. );
  71. }
  72. return (
  73. <Tag key={index} className='text-xs' bordered={tag.border ?? false} color={tag.color}>
  74. {tag.text}
  75. </Tag>
  76. );
  77. })}
  78. </div>
  79. );
  80. }, [tags]);
  81. return (
  82. <div
  83. className={classNames(
  84. 'group/card relative flex flex-col w-72 rounded justify-between text-black bg-white shadow-[0_8px_16px_-10px_rgba(100,100,100,.08)] hover:shadow-[0_14px_20px_-10px_rgba(100,100,100,.15)] dark:bg-[#232734] dark:text-white dark:hover:border-white transition-[transfrom_shadow] duration-300 hover:-translate-y-1 min-h-fit',
  85. {
  86. 'grayscale cursor-no-drop': disabled,
  87. 'cursor-pointer': !disabled && !!props.onClick,
  88. },
  89. className,
  90. )}
  91. {...props}
  92. >
  93. <div className='p-4 0'>
  94. <div className='flex items-center'>
  95. {iconNode}
  96. <div className='flex flex-col'>
  97. <Popover title={title}>
  98. <h2 className='text-sm font-semibold line-clamp-1 pr-8'>{title}</h2>
  99. </Popover>
  100. {tagNode}
  101. </div>
  102. </div>
  103. {desc && (
  104. <Tooltip title={desc}>
  105. <p className='mt-2 text-sm text-gray-500 font-normal line-clamp-2'>{desc}</p>
  106. </Tooltip>
  107. )}
  108. </div>
  109. <div>
  110. {children}
  111. {operations && !!operations.length && (
  112. <div className='flex flex-wrap items-center justify-center border-t border-solid border-gray-100 dark:border-theme-dark'>
  113. {operations.map((item, index) => (
  114. <Tooltip key={`operation-${index}`} title={item.label}>
  115. <div
  116. className='relative flex flex-1 items-center justify-center h-11 text-gray-400 hover:text-blue-500 transition-colors duration-300 cursor-pointer'
  117. onClick={e => {
  118. e.stopPropagation();
  119. item.onClick?.();
  120. }}
  121. >
  122. {item.children}
  123. {index < operations.length - 1 && (
  124. <div className='w-[1px] h-6 absolute top-2 right-0 bg-gray-100 rounded dark:bg-theme-dark' />
  125. )}
  126. </div>
  127. </Tooltip>
  128. ))}
  129. </div>
  130. )}
  131. </div>
  132. <div className='absolute top-2 right-4 '>{extraContent}</div>
  133. </div>
  134. );
  135. }
  136. export default memo(GPTCard);