/** @jsx jsx */ /** @jsxFrag React.Fragment */ import { type Spec } from 'immutability-helper'; import { jsx, css } from '@emotion/react'; import React from 'react'; import { Cell, Column, Table2 as Table } from '@blueprintjs/table'; import { Callout, Button, ButtonGroup } from '@blueprintjs/core'; import { PropertyDetailView } from '@riboseinc/paneron-registry-kit'; export type ColumnInfo = { [K in keyof T]: { title: string width: number CellRenderer: (props: { val: T[K] onChange?: (spec: Spec) => void item: T itemIndex: number }) => JSX.Element } } interface ItemTableProps { items: readonly T[]; /** Singular item label, lowercase. */ itemLabel: string /** Plural item label, lowercase. */ itemLabelPlural?: string /** Same as for BP’s FormGroup. */ helperText?: React.ReactNode /** Same as for BP’s FormGroup. */ subLabel?: React.ReactNode /** Required for being able to either remove, edit or create new items. */ onChangeItems?: (spec: Spec>) => void maxItems?: number minItems?: number /** * Required for being able to create new items * and for calculating things like column numbers. */ placeholderItem: T | (() => T) columnInfo: ColumnInfo rowHeight?: number //CellRenderer: (opts: { // val: T[K] // onClear?: () => void // onChange?: (spec: Spec) => void // item: T //}) => JSX.Element } export function ItemTable ({ items, itemLabel, itemLabelPlural, helperText, subLabel, onChangeItems, placeholderItem, columnInfo, rowHeight, maxItems, minItems, }: ItemTableProps) { //const placeholder = typeof placeholderItem === 'function' // ? (placeholderItem as () => T)() // : placeholderItem; const pluralLabel = itemLabelPlural || `${itemLabel} items`; const countSummary = items.length > 0 ? <>({items.length} total) : <>(no items to show); const handleAddNew = React.useMemo(() => ( placeholderItem !== undefined && onChangeItems ? async function handleAddNewItem() { const newItem: T = typeof placeholderItem === 'function' ? await (placeholderItem as (() => T) | (() => Promise))() : placeholderItem; if (newItem !== undefined) { onChangeItems({ $push: [newItem] }); } } : null ), [onChangeItems, placeholderItem]); const addButton = React.useMemo(() => ( handleAddNew ? : null ), [handleAddNew, itemLabel]); const deleteAllButton = React.useMemo(() => ( onChangeItems && items.length > 1 ? : null ), [onChangeItems, pluralLabel, items.length > 1]); const countValidity = (maxItems !== undefined && items.length > maxItems) ? `Too many ${pluralLabel}: at most ${maxItems} is expected` : (minItems !== undefined && items.length < minItems) ? `Not enough ${pluralLabel}: at least ${minItems} is expected` : ''; const handleItemChange = React.useMemo(() => { if (onChangeItems) { return (rowIndex: number) => (spec: Spec) => onChangeItems({ [rowIndex]: spec }); } else { return undefined; } }, [items, onChangeItems]); function renderCell (rowIndex: number, fieldName: F) { const item = items[rowIndex]; const handleChange = handleItemChange?.(rowIndex); const CellRenderer = columnInfo[fieldName].CellRenderer return ( handleChange({ [fieldName]: spec } as Spec) : undefined} item={item} itemIndex={rowIndex} /> ); }; const columnInfoEntries = React.useMemo(() => ( Object.entries(columnInfo). map(([key, cfg]) => [key as keyof T, cfg as ColumnInfo[keyof T]] as const) ), [columnInfo]); const renderers = React.useMemo(() => ( columnInfoEntries. map(([key, ]) => { return { [key]: (rowIndex: number) => { return renderCell(rowIndex, key); } }; }). reduce((prev, curr) => ({ ...prev, ...curr }) ) as { [K in keyof T]: (rowIndex: number) => JSX.Element } ), [columnInfoEntries, handleItemChange, items]); return ( {helperText} {countValidity && onChangeItems ? {countValidity} : null} {(addButton || deleteAllButton) ? 0 ? '10px' : '0'};`}> {addButton} {deleteAllButton} : null} : null}> rowHeight ?? 36)} columnWidths={columnInfoEntries.map(([, cfg]) => cfg.width)}> {columnInfoEntries.map(([key, cfg]) => )}
); } export default ItemTable;