BucketTableRow.tsx157 lines · main
1import { FilesBucket as FilesBucketIcon } from 'icons'
2import { ChevronRight } from 'lucide-react'
3import { KeyboardEventHandler, MouseEventHandler } from 'react'
4import { Badge, cn, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
5
6import type { AllowedBucketType } from './types'
7import { PUBLIC_BUCKET_TOOLTIP } from '@/components/interfaces/Storage/Storage.constants'
8import { useBucketPolicyCount } from '@/components/interfaces/Storage/useBucketPolicyCount'
9import { VirtualizedTableCell, VirtualizedTableRow } from '@/components/ui/VirtualizedTable'
10import { Bucket } from '@/data/storage/buckets-query'
11import { formatBytes } from '@/lib/helpers'
12
13type BucketTableMode = 'standard' | 'virtualized'
14
15type BucketTableEmptyStateProps = {
16 mode: BucketTableMode
17 filterString: string
18}
19
20export const BucketTableEmptyState = ({ mode, filterString }: BucketTableEmptyStateProps) => {
21 const BucketTableRow = mode === 'standard' ? TableRow : VirtualizedTableRow
22 const BucketTableCell = mode === 'standard' ? TableCell : VirtualizedTableCell
23
24 return (
25 <BucketTableRow className="[&>td]:hover:bg-inherit">
26 <BucketTableCell colSpan={5}>
27 <p className="text-sm text-foreground">No results found</p>
28 <p className="text-sm text-foreground-lighter">
29 Your search for “{filterString}” did not return any results
30 </p>
31 </BucketTableCell>
32 </BucketTableRow>
33 )
34}
35
36type BucketTableRowProps = {
37 mode: BucketTableMode
38 bucket: Bucket
39 onSelectBucket: (bucket: Bucket) => void
40 allowedBucketType: AllowedBucketType
41 formattedGlobalUploadLimit: string
42}
43
44export const BucketTableRow = ({
45 mode,
46 bucket,
47 onSelectBucket,
48 allowedBucketType,
49 formattedGlobalUploadLimit,
50}: BucketTableRowProps) => {
51 const { getPolicyCount } = useBucketPolicyCount()
52
53 const BucketTableRow = mode === 'standard' ? TableRow : VirtualizedTableRow
54 const BucketTableCell = mode === 'standard' ? TableCell : VirtualizedTableCell
55
56 const isDisabled = !(
57 allowedBucketType === 'all' ||
58 (allowedBucketType === 'public' && bucket.public) ||
59 (allowedBucketType === 'private' && !bucket.public)
60 )
61
62 const handleRowActivate: MouseEventHandler<HTMLTableRowElement> = (e) => {
63 e.preventDefault()
64 if (isDisabled) return
65 onSelectBucket(bucket)
66 }
67
68 const handleRowKeyDown: KeyboardEventHandler<HTMLTableRowElement> = (e) => {
69 if (isDisabled) return
70 if (e.key === 'Enter' || e.key === ' ') {
71 e.preventDefault()
72 onSelectBucket(bucket)
73 }
74 }
75
76 return (
77 <Tooltip>
78 <TooltipTrigger asChild>
79 <BucketTableRow
80 key={bucket.id}
81 data-bucket-id={bucket.id}
82 className={cn(
83 'relative cursor-pointer h-16 group inset-focus',
84 isDisabled && 'opacity-50 [&>td]:hover:bg-transparent cursor-not-allowed'
85 )}
86 onClick={handleRowActivate}
87 onKeyDown={handleRowKeyDown}
88 tabIndex={isDisabled ? -1 : 0}
89 aria-disabled={isDisabled || undefined}
90 >
91 <BucketTableCell className="w-2 pr-1">
92 <FilesBucketIcon aria-label="bucket icon" size={16} className="text-foreground-muted" />
93 </BucketTableCell>
94 <BucketTableCell className="flex-1">
95 <div className="flex items-center gap-2.5">
96 <p className="whitespace-nowrap max-w-[512px] truncate">{bucket.id}</p>
97 {bucket.public && (
98 <Tooltip>
99 <TooltipTrigger asChild>
100 <Badge variant="warning" className="flex">
101 Public
102 </Badge>
103 </TooltipTrigger>
104 <TooltipContent side="top">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
105 </Tooltip>
106 )}
107 </div>
108 </BucketTableCell>
109
110 <BucketTableCell>
111 <p className="text-foreground-light">{getPolicyCount(bucket.id)}</p>
112 </BucketTableCell>
113
114 <BucketTableCell>
115 <p
116 className={`whitespace-nowrap ${bucket.file_size_limit ? 'text-foreground-light' : 'text-foreground-muted'}`}
117 >
118 {bucket.file_size_limit
119 ? formatBytes(bucket.file_size_limit)
120 : `Unset (${formattedGlobalUploadLimit})`}
121 </p>
122 </BucketTableCell>
123
124 <BucketTableCell>
125 <p
126 className={
127 bucket.allowed_mime_types ? 'text-foreground-light' : 'text-foreground-muted'
128 }
129 >
130 {bucket.allowed_mime_types ? bucket.allowed_mime_types.join(', ') : 'Any'}
131 </p>
132 </BucketTableCell>
133
134 <BucketTableCell>
135 {!isDisabled && (
136 <>
137 <div className="flex justify-end items-center h-full">
138 <ChevronRight aria-hidden={true} size={14} className="text-foreground-muted/60" />
139 </div>
140 <button tabIndex={-1} className="sr-only">
141 Go to bucket details
142 </button>
143 </>
144 )}
145 </BucketTableCell>
146 </BucketTableRow>
147 </TooltipTrigger>
148 {isDisabled && (
149 <TooltipContent>
150 {allowedBucketType === 'public'
151 ? 'Private buckets are not selectable for this action. Please select a public bucket.'
152 : 'Public buckets are not selectable for this action. Please select a private bucket.'}
153 </TooltipContent>
154 )}
155 </Tooltip>
156 )
157}