edbf68909a
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>
64 lines
2.3 KiB
React
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>
|
|
)
|
|
}
|