Files
tolaria/src/components/BulkActionBar.tsx
2026-04-24 22:28:07 +02:00

106 lines
3.4 KiB
TypeScript

import { memo } from 'react'
import { Archive, ArrowCounterClockwise, CheckCircle, Trash, X } from '@phosphor-icons/react'
import { Button } from '@/components/ui/button'
interface BulkActionBarProps {
count: number
isArchivedView?: boolean
onOrganize?: () => void
onArchive: () => void
onDelete: () => void
onUnarchive?: () => void
onClear: () => void
}
interface BulkActionButtonProps {
ariaLabel: string
children: React.ReactNode
destructive?: boolean
onClick?: () => void
testId: string
}
function BulkActionButton({ ariaLabel, children, destructive = false, onClick, testId }: BulkActionButtonProps) {
return (
<Button
type="button"
size="icon-sm"
variant={destructive ? 'destructive' : 'ghost'}
className={
destructive
? 'h-8 w-8 rounded-lg bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/30'
: 'h-8 w-8 rounded-lg bg-background/10 text-background hover:bg-background/20 focus-visible:ring-background/35 disabled:bg-background/5 disabled:text-background/35'
}
onClick={onClick}
disabled={!onClick}
aria-label={ariaLabel}
title={ariaLabel}
data-testid={testId}
>
{children}
</Button>
)
}
function renderPrimaryActions(
isArchivedView: boolean,
onOrganize: (() => void) | undefined,
onArchive: () => void,
onDelete: () => void,
onUnarchive: (() => void) | undefined,
) {
const archiveLabel = isArchivedView ? 'Unarchive selected notes' : 'Archive selected notes'
const archiveIcon = isArchivedView ? <ArrowCounterClockwise size={16} /> : <Archive size={16} />
const archiveHandler = isArchivedView ? onUnarchive : onArchive
return (
<>
<BulkActionButton ariaLabel="Organize selected notes" onClick={onOrganize} testId="bulk-organize-btn">
<CheckCircle size={16} weight="fill" />
</BulkActionButton>
<BulkActionButton ariaLabel={archiveLabel} onClick={archiveHandler} testId={isArchivedView ? 'bulk-unarchive-btn' : 'bulk-archive-btn'}>
{archiveIcon}
</BulkActionButton>
<BulkActionButton ariaLabel="Permanently delete selected notes" destructive onClick={onDelete} testId="bulk-delete-btn">
<Trash size={16} />
</BulkActionButton>
</>
)
}
function BulkActionBarInner({ count, isArchivedView, onOrganize, onArchive, onDelete, onUnarchive, onClear }: BulkActionBarProps) {
return (
<div
className="flex shrink-0 items-center justify-between"
style={{
height: 44,
padding: '0 12px',
background: 'var(--foreground)',
color: 'var(--background)',
}}
data-testid="bulk-action-bar"
>
<span style={{ fontSize: 13, fontWeight: 500 }}>
{count} selected
</span>
<div className="flex items-center gap-1.5">
{renderPrimaryActions(Boolean(isArchivedView), onOrganize, onArchive, onDelete, onUnarchive)}
<Button
type="button"
size="icon-sm"
variant="ghost"
className="h-8 w-8 rounded-lg text-background/55 hover:bg-background/10 hover:text-background focus-visible:ring-background/30"
onClick={onClear}
aria-label="Clear selection"
title="Clear selection"
data-testid="bulk-clear-btn"
>
<X size={16} />
</Button>
</div>
</div>
)
}
export const BulkActionBar = memo(BulkActionBarInner)