ResourceItem.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { ChevronRight, MoreVertical } from 'lucide-react'
  2. import Link from 'next/link'
  3. import { forwardRef, HTMLAttributes, KeyboardEvent, ReactNode } from 'react'
  4. import {
  5. Button,
  6. CardContent,
  7. cn,
  8. DropdownMenu,
  9. DropdownMenuContent,
  10. DropdownMenuItem,
  11. DropdownMenuTrigger,
  12. } from 'ui'
  13. export interface ResourceAction {
  14. label: string
  15. onClick: () => void
  16. }
  17. export interface ResourceItemProps extends HTMLAttributes<HTMLDivElement> {
  18. media?: ReactNode
  19. meta?: ReactNode
  20. onClick?: () => void
  21. children?: ReactNode
  22. actions?: ResourceAction[]
  23. href?: string
  24. target?: string
  25. rel?: string
  26. }
  27. export const ResourceItem = forwardRef<HTMLDivElement, ResourceItemProps>(
  28. (
  29. {
  30. media,
  31. meta,
  32. onClick,
  33. children,
  34. className,
  35. actions,
  36. href,
  37. target,
  38. rel,
  39. onKeyDown,
  40. role,
  41. tabIndex,
  42. ...props
  43. },
  44. ref
  45. ) => {
  46. const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
  47. onKeyDown?.(event)
  48. if (event.defaultPrevented || !onClick || event.target !== event.currentTarget) return
  49. if (event.key === 'Enter' || event.key === ' ') {
  50. event.preventDefault()
  51. onClick()
  52. }
  53. }
  54. const content = (
  55. <>
  56. {media && (
  57. <div className="text-foreground-light flex items-center justify-center">{media}</div>
  58. )}
  59. <div className="flex-1">{children}</div>
  60. {meta && <div>{meta}</div>}
  61. {actions && actions.length > 0 ? (
  62. <DropdownMenu>
  63. <DropdownMenuTrigger asChild>
  64. <Button
  65. type="text"
  66. className="px-1"
  67. icon={<MoreVertical size={16} />}
  68. onClick={(e) => {
  69. e.stopPropagation()
  70. }}
  71. />
  72. </DropdownMenuTrigger>
  73. <DropdownMenuContent align="end">
  74. {actions.map((action) => (
  75. <DropdownMenuItem
  76. key={action.label}
  77. onClick={(e) => {
  78. e.stopPropagation()
  79. action.onClick()
  80. }}
  81. >
  82. {action.label}
  83. </DropdownMenuItem>
  84. ))}
  85. </DropdownMenuContent>
  86. </DropdownMenu>
  87. ) : (
  88. onClick && <ChevronRight strokeWidth={1.5} size={16} />
  89. )}
  90. </>
  91. )
  92. const rootClassName = cn(
  93. 'flex items-center justify-between text-sm gap-4',
  94. 'border-b-0!',
  95. (onClick || href) && 'cursor-pointer transition-colors duration-150 hover:bg-surface-200',
  96. className
  97. )
  98. if (href) {
  99. return (
  100. <Link
  101. href={href}
  102. target={target}
  103. rel={rel}
  104. className={cn('py-4 px-(--card-padding-x) border-b last:border-none', rootClassName)}
  105. >
  106. {content}
  107. </Link>
  108. )
  109. }
  110. return (
  111. <CardContent
  112. ref={ref}
  113. className={rootClassName}
  114. onClick={onClick}
  115. onKeyDown={handleKeyDown}
  116. role={onClick ? 'button' : role}
  117. tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
  118. {...props}
  119. >
  120. {content}
  121. </CardContent>
  122. )
  123. }
  124. )
  125. ResourceItem.displayName = 'ResourceItem'