11import { Eye , EyeOff , Search , Settings , Trash2 } from 'lucide-react'
22import { useMemo , useState } from 'react'
33import { twMerge } from 'tailwind-merge'
4- import Badge from '@/components/Badge'
54import { badgeCVA } from '@/components/design'
65import MultiSegment from '@/components/MultiSegment'
7- import { allLeafValues , timeAgo } from '@/components/misc'
6+ import { allLeafValues } from '@/components/misc'
87import type { CommentTableRow } from '@/entrypoints/background'
98import 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'
1113
1214const initialFilter : FilterState = {
1315 searchQuery : '' ,
@@ -20,7 +22,7 @@ interface PopupRootProps {
2022}
2123
2224export function PopupRoot ( { drafts } : PopupRootProps ) {
23- const [ selectedIds , setSelectedIds ] = useState ( new Set ( ) )
25+ const [ selectedIds , setSelectedIds ] = useState < Set < string > > ( new Set ( ) )
2426 const [ filters , setFilters ] = useState < FilterState > ( initialFilter )
2527
2628 const updateFilter = < K extends keyof FilterState > ( key : K , value : FilterState [ K ] ) => {
@@ -100,31 +102,22 @@ export function PopupRoot({ drafts }: PopupRootProps) {
100102 return < NoMatchesState onClearFilters = { clearFilters } />
101103 }
102104
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+ ) )
106115 }
107116
108117 return (
109118 < div className = 'bg-white' >
110119 { /* 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 } /> }
128121
129122 { /* Table */ }
130123 < div className = 'overflow-x-auto' >
@@ -221,90 +214,3 @@ export function PopupRoot({ drafts }: PopupRootProps) {
221214 </ div >
222215 )
223216}
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