BucketTable.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { FilesBucket as FilesBucketIcon } from 'icons'
  2. import { ChevronRight } from 'lucide-react'
  3. import { useRouter } from 'next/navigation'
  4. import {
  5. Badge,
  6. TableCell,
  7. TableHead,
  8. TableHeader,
  9. TableRow,
  10. Tooltip,
  11. TooltipContent,
  12. TooltipTrigger,
  13. } from 'ui'
  14. import { PUBLIC_BUCKET_TOOLTIP } from '@/components/interfaces/Storage/Storage.constants'
  15. import { useBucketPolicyCount } from '@/components/interfaces/Storage/useBucketPolicyCount'
  16. import {
  17. VirtualizedTableCell,
  18. VirtualizedTableHead,
  19. VirtualizedTableHeader,
  20. VirtualizedTableRow,
  21. } from '@/components/ui/VirtualizedTable'
  22. import { Bucket } from '@/data/storage/buckets-query'
  23. import { formatBytes } from '@/lib/helpers'
  24. import { createNavigationHandler } from '@/lib/navigation'
  25. type BucketTableMode = 'standard' | 'virtualized'
  26. type BucketTableHeaderProps = {
  27. mode: BucketTableMode
  28. hasBuckets?: boolean
  29. }
  30. export const BucketTableHeader = ({ mode, hasBuckets = true }: BucketTableHeaderProps) => {
  31. const BucketTableHeader = mode === 'standard' ? TableHeader : VirtualizedTableHeader
  32. const BucketTableRow = mode === 'standard' ? TableRow : VirtualizedTableRow
  33. const BucketTableHead = mode === 'standard' ? TableHead : VirtualizedTableHead
  34. const stickyClasses = 'sticky top-0 z-10 bg-200'
  35. return (
  36. <BucketTableHeader>
  37. <BucketTableRow>
  38. {hasBuckets && (
  39. <BucketTableHead className={`${stickyClasses} w-2 pr-1`}>
  40. <span className="sr-only">Icon</span>
  41. </BucketTableHead>
  42. )}
  43. <BucketTableHead className={stickyClasses}>Name</BucketTableHead>
  44. <BucketTableHead className={stickyClasses}>Policies</BucketTableHead>
  45. <BucketTableHead className={stickyClasses}>File size limit</BucketTableHead>
  46. <BucketTableHead className={stickyClasses}>Allowed MIME types</BucketTableHead>
  47. <BucketTableHead className={stickyClasses}>
  48. <span className="sr-only">Actions</span>
  49. </BucketTableHead>
  50. </BucketTableRow>
  51. </BucketTableHeader>
  52. )
  53. }
  54. type BucketTableEmptyStateProps = {
  55. mode: BucketTableMode
  56. filterString: string
  57. }
  58. export const BucketTableEmptyState = ({ mode, filterString }: BucketTableEmptyStateProps) => {
  59. const BucketTableRow = mode === 'standard' ? TableRow : VirtualizedTableRow
  60. const BucketTableCell = mode === 'standard' ? TableCell : VirtualizedTableCell
  61. return (
  62. <BucketTableRow className="[&>td]:hover:bg-inherit">
  63. <BucketTableCell colSpan={5}>
  64. <p className="text-sm text-foreground">No results found</p>
  65. <p className="text-sm text-foreground-lighter">
  66. Your search for “{filterString}” did not return any results
  67. </p>
  68. </BucketTableCell>
  69. </BucketTableRow>
  70. )
  71. }
  72. type BucketTableRowProps = {
  73. mode: BucketTableMode
  74. bucket: Bucket
  75. projectRef: string
  76. formattedGlobalUploadLimit: string
  77. }
  78. export const BucketTableRow = ({
  79. mode,
  80. bucket,
  81. projectRef,
  82. formattedGlobalUploadLimit,
  83. }: BucketTableRowProps) => {
  84. const router = useRouter()
  85. const { getPolicyCount } = useBucketPolicyCount()
  86. const BucketTableRow = mode === 'standard' ? TableRow : VirtualizedTableRow
  87. const BucketTableCell = mode === 'standard' ? TableCell : VirtualizedTableCell
  88. const handleBucketNavigation = createNavigationHandler(
  89. `/project/${projectRef}/storage/files/buckets/${encodeURIComponent(bucket.id)}`,
  90. router
  91. )
  92. return (
  93. <BucketTableRow
  94. key={bucket.id}
  95. data-bucket-id={bucket.id}
  96. className="relative cursor-pointer h-16 group inset-focus"
  97. onClick={handleBucketNavigation}
  98. onAuxClick={handleBucketNavigation}
  99. onKeyDown={handleBucketNavigation}
  100. tabIndex={0}
  101. >
  102. <BucketTableCell className="w-2 pr-1">
  103. <FilesBucketIcon aria-label="bucket icon" size={16} className="text-foreground-muted" />
  104. </BucketTableCell>
  105. <BucketTableCell className="flex-1">
  106. <div className="flex items-center gap-2.5">
  107. <p className="whitespace-nowrap max-w-[512px] truncate">{bucket.id}</p>
  108. {bucket.public && (
  109. <Tooltip>
  110. <TooltipTrigger asChild>
  111. <Badge variant="warning" className="flex">
  112. Public
  113. </Badge>
  114. </TooltipTrigger>
  115. <TooltipContent side="top">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
  116. </Tooltip>
  117. )}
  118. </div>
  119. </BucketTableCell>
  120. <BucketTableCell>
  121. <p className="text-foreground-light">{getPolicyCount(bucket.id)}</p>
  122. </BucketTableCell>
  123. <BucketTableCell>
  124. <p
  125. className={`whitespace-nowrap ${bucket.file_size_limit ? 'text-foreground-light' : 'text-foreground-muted'}`}
  126. >
  127. {bucket.file_size_limit
  128. ? formatBytes(bucket.file_size_limit)
  129. : `Unset (${formattedGlobalUploadLimit})`}
  130. </p>
  131. </BucketTableCell>
  132. <BucketTableCell>
  133. <p
  134. className={bucket.allowed_mime_types ? 'text-foreground-light' : 'text-foreground-muted'}
  135. >
  136. {bucket.allowed_mime_types ? bucket.allowed_mime_types.join(', ') : 'Any'}
  137. </p>
  138. </BucketTableCell>
  139. <BucketTableCell>
  140. <div className="flex justify-end items-center h-full">
  141. <ChevronRight aria-hidden={true} size={14} className="text-foreground-muted/60" />
  142. </div>
  143. <button tabIndex={-1} className="sr-only">
  144. Go to bucket details
  145. </button>
  146. </BucketTableCell>
  147. </BucketTableRow>
  148. )
  149. }