Files
rfc-app/frontend/src/components/BulkActionBar.jsx
T
Ben Stull edbf68909a feat(slice5): catalog multi-select + bulk action bar (§22.4a PUC-2)
Faceted catalog rows are selectable for contributors; a sticky bulk bar
(BulkActionBar) drives set/add/remove gestures from the collection fields
schema, calls bulkEntryMeta (one commit), toasts applied/skipped counts,
and re-fetches. Non-contributors and legacy (no-fields) collections see
no selection (INV-5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:51:48 -07:00

64 lines
2.3 KiB
React

import { useState } from 'react'
// §22.4a SLICE-5 (PUC-2, UX §5.3): the sticky bulk action bar shown when ≥1
// catalog row is selected. Driven by the collection `fields:` schema — one
// "Set <field>" control per enum field, and an Add/Remove tag control per
// tags field. Each gesture calls onApply({ op, field, value }); the parent
// (Catalog) sends one bulk request and re-fetches.
const labelFor = (name, def) =>
def?.label || name.charAt(0).toUpperCase() + name.slice(1)
export default function BulkActionBar({ fields, count, onApply, onClear }) {
const [tagValue, setTagValue] = useState('')
const entries = Object.entries(fields || {})
const tagField = entries.find(([, d]) => d.type === 'tags')
return (
<div className="bulk-action-bar">
<span className="bulk-count">{count} selected</span>
{entries
.filter(([, d]) => d.type === 'enum')
.map(([name, def]) => (
<label key={name} className="bulk-set">
<span>Set {labelFor(name, def)}</span>
<select
aria-label={`Set ${labelFor(name, def)}`}
value=""
onChange={e => {
if (e.target.value) onApply({ op: 'set', field: name, value: e.target.value })
}}
>
<option value=""></option>
{(def.values || []).map(v => <option key={v} value={v}>{v}</option>)}
</select>
</label>
))}
{tagField && (
<div className="bulk-tags">
<input
placeholder="tag…"
value={tagValue}
onChange={e => setTagValue(e.target.value)}
/>
<button
type="button"
disabled={!tagValue.trim()}
onClick={() => { onApply({ op: 'add', field: tagField[0], value: tagValue.trim() }); setTagValue('') }}
>
Add tag
</button>
<button
type="button"
disabled={!tagValue.trim()}
onClick={() => { onApply({ op: 'remove', field: tagField[0], value: tagValue.trim() }); setTagValue('') }}
>
Remove tag
</button>
</div>
)}
<button type="button" className="bulk-clear" onClick={onClear}>Clear</button>
</div>
)
}