1
1
import { Eye , EyeOff , Search , Settings , Trash2 } from 'lucide-react'
2
2
import { useMemo , useState } from 'react'
3
3
import { twMerge } from 'tailwind-merge'
4
- import Badge from '@/components/Badge'
5
4
import { badgeCVA } from '@/components/design'
6
5
import MultiSegment from '@/components/MultiSegment'
7
- import { allLeafValues , timeAgo } from '@/components/misc'
6
+ import { allLeafValues } from '@/components/misc'
8
7
import type { CommentTableRow } from '@/entrypoints/background'
9
8
import type { FilterState } from '@/entrypoints/popup/popup'
10
- import { EnhancerRegistry } from '@/lib/registries'
9
+ import { BulkActionsBar } from './BulkActionsBar'
10
+ import { CommentRow } from './CommentRow'
11
+ import { EmptyState } from './EmptyState'
12
+ import { NoMatchesState } from './NoMatchesState'
11
13
12
14
const initialFilter : FilterState = {
13
15
searchQuery : '' ,
@@ -20,7 +22,7 @@ interface PopupRootProps {
20
22
}
21
23
22
24
export function PopupRoot ( { drafts } : PopupRootProps ) {
23
- const [ selectedIds , setSelectedIds ] = useState ( new Set ( ) )
25
+ const [ selectedIds , setSelectedIds ] = useState < Set < string > > ( new Set ( ) )
24
26
const [ filters , setFilters ] = useState < FilterState > ( initialFilter )
25
27
26
28
const updateFilter = < K extends keyof FilterState > ( key : K , value : FilterState [ K ] ) => {
@@ -100,31 +102,22 @@ export function PopupRoot({ drafts }: PopupRootProps) {
100
102
return < NoMatchesState onClearFilters = { clearFilters } />
101
103
}
102
104
103
- return filteredDrafts . map ( ( row ) =>
104
- commentRow ( row , selectedIds , toggleSelection , handleOpen , handleTrash ) ,
105
- )
105
+ return filteredDrafts . map ( ( row ) => (
106
+ < CommentRow
107
+ key = { row . spot . unique_key }
108
+ row = { row }
109
+ selectedIds = { selectedIds }
110
+ toggleSelection = { toggleSelection }
111
+ handleOpen = { handleOpen }
112
+ handleTrash = { handleTrash }
113
+ />
114
+ ) )
106
115
}
107
116
108
117
return (
109
118
< div className = 'bg-white' >
110
119
{ /* Bulk actions bar - floating popup */ }
111
- { selectedIds . size > 0 && (
112
- < div className = '-translate-x-1/2 fixed bottom-6 left-1/2 z-50 flex transform items-center gap-3 rounded-md border border-blue-200 bg-blue-50 p-3 shadow-lg' >
113
- < span className = 'font-medium text-sm' > { selectedIds . size } selected</ span >
114
- < button type = 'button' className = 'text-blue-600 text-sm hover:underline' >
115
- Copy
116
- </ button >
117
- < button type = 'button' className = 'text-blue-600 text-sm hover:underline' >
118
- Preview
119
- </ button >
120
- < button type = 'button' className = 'text-blue-600 text-sm hover:underline' >
121
- Discard
122
- </ button >
123
- < button type = 'button' className = 'text-blue-600 text-sm hover:underline' >
124
- Open
125
- </ button >
126
- </ div >
127
- ) }
120
+ { selectedIds . size > 0 && < BulkActionsBar selectedIds = { selectedIds } /> }
128
121
129
122
{ /* Table */ }
130
123
< div className = 'overflow-x-auto' >
@@ -221,90 +214,3 @@ export function PopupRoot({ drafts }: PopupRootProps) {
221
214
</ div >
222
215
)
223
216
}
224
-
225
- const enhancers = new EnhancerRegistry ( )
226
- function commentRow (
227
- row : CommentTableRow ,
228
- selectedIds : Set < unknown > ,
229
- toggleSelection : ( id : string ) => void ,
230
- _handleOpen : ( url : string ) => void ,
231
- _handleTrash : ( row : CommentTableRow ) => void ,
232
- ) {
233
- const enhancer = enhancers . enhancerFor ( row . spot )
234
- return (
235
- < tr key = { row . spot . unique_key } className = 'hover:bg-gray-50' >
236
- < td className = 'px-3 py-3' >
237
- < input
238
- type = 'checkbox'
239
- checked = { selectedIds . has ( row . spot . unique_key ) }
240
- onChange = { ( ) => toggleSelection ( row . spot . unique_key ) }
241
- className = 'rounded'
242
- />
243
- </ td >
244
- < td className = 'px-3 py-3' >
245
- < div className = 'space-y-1' >
246
- { /* Context line */ }
247
- < div className = 'flex items-center justify-between gap-1.5 text-gray-600 text-xs' >
248
- < div className = 'flex min-w-0 flex-1 items-center gap-1.5' >
249
- { enhancer . tableUpperDecoration ( row . spot ) }
250
- </ div >
251
- < div className = 'flex flex-shrink-0 items-center gap-1' >
252
- { row . latestDraft . stats . links . length > 0 && (
253
- < Badge type = 'link' text = { row . latestDraft . stats . links . length } />
254
- ) }
255
- { row . latestDraft . stats . images . length > 0 && (
256
- < Badge type = 'image' text = { row . latestDraft . stats . images . length } />
257
- ) }
258
- { row . latestDraft . stats . codeBlocks . length > 0 && (
259
- < Badge type = 'code' text = { row . latestDraft . stats . codeBlocks . length } />
260
- ) }
261
- < Badge type = 'text' text = { row . latestDraft . stats . charCount } />
262
- < Badge type = 'time' text = { timeAgo ( row . latestDraft . time ) } />
263
- { row . isOpenTab && < Badge type = 'open' /> }
264
- </ div >
265
- </ div >
266
-
267
- { /* Title */ }
268
- < div className = 'flex items-center gap-1' >
269
- < a href = 'TODO' className = 'truncate font-medium text-sm hover:underline' >
270
- { enhancer . tableTitle ( row . spot ) }
271
- </ a >
272
- < Badge type = { row . isSent ? 'sent' : 'unsent' } />
273
- { row . isTrashed && < Badge type = 'trashed' /> }
274
- </ div >
275
- { /* Draft */ }
276
- < div className = 'truncate text-sm' >
277
- < span className = 'text-gray-500' > { row . latestDraft . content . substring ( 0 , 100 ) } …</ span >
278
- </ div >
279
- </ div >
280
- </ td >
281
- </ tr >
282
- )
283
- }
284
-
285
- const EmptyState = ( ) => (
286
- < div className = 'mx-auto max-w-4xl py-16 text-center' >
287
- < h2 className = 'mb-4 font-semibold text-2xl' > No comments open</ h2 >
288
- < p className = 'mb-6 text-gray-600' >
289
- Your drafts will appear here when you start typing in comment boxes across GitHub and Reddit.
290
- </ p >
291
- < div className = 'space-y-2' >
292
- < button type = 'button' className = 'text-blue-600 hover:underline' >
293
- How it works
294
- </ button >
295
- < span className = 'mx-2' > ·</ span >
296
- < button type = 'button' className = 'text-blue-600 hover:underline' >
297
- Check permissions
298
- </ button >
299
- </ div >
300
- </ div >
301
- )
302
-
303
- const NoMatchesState = ( { onClearFilters } : { onClearFilters : ( ) => void } ) => (
304
- < div className = 'py-16 text-center' >
305
- < p className = 'mb-4 text-gray-600' > No matches found</ p >
306
- < button type = 'button' onClick = { onClearFilters } className = 'text-blue-600 hover:underline' >
307
- Clear filters
308
- </ button >
309
- </ div >
310
- )
0 commit comments