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