PivotViewer - Renderers
Card Renderer
Section titled “Card Renderer”The card renderer determines how each item appears in the collection view. It returns structured text data — { title, labels?, values? } — and PivotViewer lays it out into the card. The labels and values arrays are paired positionally.
Basic Card
Section titled “Basic Card”const cardRenderer = (item: Product) => ({ title: item.name, labels: ['Price'], values: [`$${item.price}`]});Rich Card Example
Section titled “Rich Card Example”const taskCardRenderer = (item: Task) => ({ title: item.title, labels: ['Priority', 'Status', 'Assignee', 'Estimate'], values: [ `P${item.priority}`, item.status, item.assignee, `${item.estimatedHours}h` ]});Detail Renderer
Section titled “Detail Renderer”The detail renderer shows expanded information when an item is selected.
Basic Details
Section titled “Basic Details”const detailRenderer = (item: Product) => ( <div className="product-details"> <h2>{item.name}</h2> <img src={item.imageUrl} alt={item.name} className="detail-image" /> <p className="description">{item.description}</p> <div className="specs"> <p><strong>SKU:</strong> {item.sku}</p> <p><strong>Price:</strong> ${item.price}</p> <p><strong>Stock:</strong> {item.stock} units</p> </div> </div>);Comprehensive Details
Section titled “Comprehensive Details”const taskDetailRenderer = (item: Task) => ( <div className="task-details"> <div className="detail-header"> <h2>{item.title}</h2> <div className="header-badges"> <span className={`priority-badge p-${item.priority}`}> Priority {item.priority} </span> <span className={`status-badge status-${item.status}`}> {item.status} </span> </div> </div>
<div className="detail-section"> <h3>Description</h3> <p>{item.description}</p> </div>
<div className="detail-section"> <h3>Assignment</h3> <div className="assignee-info"> <img src={item.assigneeAvatar} alt={item.assignee} /> <div> <strong>{item.assignee}</strong> <p>{item.assigneeEmail}</p> </div> </div> </div>
<div className="detail-section"> <h3>Timeline</h3> <div className="timeline"> <p><strong>Created:</strong> {formatDate(item.createdAt)}</p> <p><strong>Due:</strong> {formatDate(item.dueDate)}</p> <p><strong>Estimated:</strong> {item.estimatedHours} hours</p> </div> </div>
<div className="detail-section"> <h3>Tags</h3> <div className="tags-list"> {item.tags.map(tag => ( <span key={tag} className="tag-pill">{tag}</span> ))} </div> </div>
{item.attachments && item.attachments.length > 0 && ( <div className="detail-section"> <h3>Attachments</h3> <ul className="attachments-list"> {item.attachments.map(att => ( <li key={att.id}> <a href={att.url}>{att.name}</a> </li> ))} </ul> </div> )} </div>);Interactive Details
Section titled “Interactive Details”Add interactive elements to the detail panel:
const interactiveDetailRenderer = (item: Task) => { const [editing, setEditing] = useState(false); const [notes, setNotes] = useState(item.notes);
const handleSaveNotes = async () => { await updateTask(item.id, { notes }); setEditing(false); };
return ( <div className="task-details-interactive"> <h2>{item.title}</h2>
{/* ... other sections ... */}
<div className="detail-section"> <div className="section-header"> <h3>Notes</h3> <button onClick={() => setEditing(!editing)}> {editing ? 'Cancel' : 'Edit'} </button> </div>
{editing ? ( <div> <textarea value={notes} onChange={(e) => setNotes(e.target.value)} rows={5} /> <button onClick={handleSaveNotes}>Save</button> </div> ) : ( <p>{notes || 'No notes'}</p> )} </div>
<div className="detail-actions"> <button onClick={() => handleStatusChange(item.id)}> Change Status </button> <button onClick={() => handleReassign(item.id)}> Reassign </button> <button onClick={() => handleDelete(item.id)}> Delete </button> </div> </div> );};Conditional Rendering
Section titled “Conditional Rendering”Adapt the structured card data based on item properties:
const adaptiveCardRenderer = (item: Task) => { const isOverdue = new Date(item.dueDate) < new Date(); const isHighPriority = item.priority >= 8;
const labels: string[] = ['Status']; const values: string[] = [item.status];
if (isOverdue) { labels.push('Flag'); values.push('Overdue'); } if (isHighPriority) { labels.push('Priority'); values.push('High'); }
return { title: item.title, labels, values };};Best Practices
Section titled “Best Practices”- Keep cards compact: Display essential info only
- Use visual hierarchy: Most important info should stand out
- Consistent sizing: All cards should be similar size
- Show preview: Give enough context to identify items
- Use icons and colors: Visual cues help quick scanning
- Limit text: Truncate long content with ellipsis
- Include imagery: Photos/icons make cards more scannable
Details
Section titled “Details”- Show comprehensive info: This is the place for all details
- Organize in sections: Group related information
- Make it actionable: Include relevant actions/buttons
- Load related data: Fetch additional details as needed
- Use tabs for complex data: Don’t overwhelm with single view
- Show relationships: Link to related items
- Include metadata: Timestamps, IDs, version info
- Consider mobile: Ensure details work on small screens