CardButton.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { ChevronRight, Loader } from 'lucide-react'
  2. import Link from 'next/link'
  3. import React, { cloneElement, PropsWithChildren } from 'react'
  4. import { cn } from 'ui'
  5. interface CardButtonProps {
  6. title?: string | React.ReactNode
  7. description?: string
  8. footer?: React.ReactNode
  9. url?: string
  10. linkHref?: string
  11. imgUrl?: string
  12. imgAlt?: string
  13. onClick?: () => void
  14. icon?: React.ReactNode
  15. loading?: boolean
  16. className?: string
  17. fixedHeight?: boolean
  18. hideChevron?: boolean
  19. titleClass?: string
  20. containerElement?: React.ReactNode
  21. }
  22. // Define separate interfaces for each type of container
  23. interface LinkContainerProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'title'> {
  24. href: string
  25. }
  26. interface UrlContainerProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'title'> {
  27. href: string
  28. }
  29. interface NonLinkContainerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {}
  30. interface ButtonContainerProps extends Omit<
  31. React.ButtonHTMLAttributes<HTMLButtonElement>,
  32. 'title'
  33. > {}
  34. // Union of all container props
  35. type ContainerProps =
  36. | LinkContainerProps
  37. | UrlContainerProps
  38. | NonLinkContainerProps
  39. | ButtonContainerProps
  40. const CardButton = ({
  41. title,
  42. description,
  43. children,
  44. footer,
  45. url = '',
  46. linkHref = '',
  47. imgUrl,
  48. imgAlt,
  49. icon,
  50. className,
  51. loading = false,
  52. fixedHeight = true,
  53. hideChevron = false,
  54. titleClass = '',
  55. containerElement,
  56. ...props
  57. }: PropsWithChildren<CardButtonProps & ContainerProps>) => {
  58. const isLink = url || linkHref || props.onClick
  59. let Container: React.ElementType
  60. let containerProps: ContainerProps = {}
  61. const ContainerComponentOverride =
  62. containerElement && React.isValidElement(containerElement)
  63. ? (props: any) => cloneElement<any>(containerElement, { ...props })
  64. : undefined
  65. if (props.onClick) {
  66. Container = ContainerComponentOverride ?? 'button'
  67. containerProps = props
  68. } else if (linkHref) {
  69. Container = ContainerComponentOverride ?? Link
  70. containerProps = {
  71. href: linkHref,
  72. ...props,
  73. }
  74. } else if (url) {
  75. Container = ContainerComponentOverride ?? 'a'
  76. containerProps = {
  77. href: url,
  78. ...props,
  79. }
  80. } else {
  81. Container = ContainerComponentOverride ?? 'div'
  82. containerProps = props
  83. }
  84. let containerClasses = [
  85. 'group relative text-left',
  86. 'bg-surface-100',
  87. 'border border-surface',
  88. 'rounded-md p-5 flex flex-row',
  89. 'transition ease-in-out duration-150',
  90. ]
  91. if (isLink) {
  92. containerClasses = [
  93. ...containerClasses,
  94. 'cursor-pointer',
  95. 'hover:bg-surface-200',
  96. 'hover:border-control',
  97. ]
  98. }
  99. if (fixedHeight) {
  100. containerClasses = [...containerClasses, 'min-h-32 md:min-h-44']
  101. }
  102. const ImageContainer = ({ children }: { children: React.ReactNode }) => {
  103. return <div className="mr-4 flex flex-col">{children}</div>
  104. }
  105. const contents = (
  106. <>
  107. {imgUrl && (
  108. <ImageContainer>
  109. <img
  110. className="
  111. transition-all
  112. group-hover:scale-110
  113. "
  114. src={`${imgUrl}`}
  115. alt={`${imgAlt}`}
  116. width="26"
  117. />
  118. </ImageContainer>
  119. )}
  120. {icon && <ImageContainer>{icon}</ImageContainer>}
  121. <div className="flex h-full w-full flex-col space-y-2">
  122. {typeof title === 'string' ? (
  123. <h5 className={`text-foreground pr-5 ${titleClass}`}>{title}</h5>
  124. ) : (
  125. title
  126. )}
  127. {(children || description) && (
  128. <div className="flex w-full flex-1 flex-col">
  129. <p className="text-sm text-foreground-light">{description}</p>
  130. <div className="w-full">{children && children}</div>
  131. </div>
  132. )}
  133. {footer && <div className="w-full mt-auto!">{footer}</div>}
  134. </div>
  135. {isLink && (
  136. <div
  137. className="
  138. absolute
  139. right-4
  140. top-4
  141. text-foreground-lighter
  142. transition-all
  143. duration-200
  144. group-hover:right-3
  145. group-hover:text-foreground
  146. "
  147. >
  148. {loading ? <Loader className="animate-spin" /> : !hideChevron ? <ChevronRight /> : <></>}
  149. </div>
  150. )}
  151. </>
  152. )
  153. return (
  154. <Container {...containerProps} className={cn(containerClasses, className)}>
  155. {contents}
  156. </Container>
  157. )
  158. }
  159. export default CardButton