import { slugify } from "@maykin-ui/client-common"; import clsx from "clsx"; import React, { useCallback } from "react"; import { FieldErrors, FieldSet, FormValidator, Validator, dataByFieldsets, validateForm, validateRequired, } from "../../../lib"; import { Column, Grid } from "../../layout"; import { Toolbar } from "../../toolbar"; import { Body, H2, Hr } from "../../typography"; import { AttributeList, AttributeListProps } from "../attributelist"; import { ValueEditableUnion, ValueProps } from "../value"; import "./attributegrid.scss"; export type AttributeGridProps = { /** The object to show object attributes of. */ object: T; /** The fieldsets to render. */ fieldsets: FieldSet[]; /** Props for AttributeList. */ attributeListProps?: Partial>; /* Whether to decorate the value, if applicable. */ decorate?: boolean; /** Error messages, applied to automatically rendered field. */ errors?: FieldErrors | Partial>; /** Whether to use `height 100%;`. */ fullHeight?: boolean; /** Whether to automatically generate id's for titles. */ generateTitleIds?: boolean; /** A title. */ title?: React.ReactNode; /** A validation function. */ validate?: FormValidator; /** If set, use this custom set of `Validator`s. */ validators?: Validator[]; /** Props for Value. */ valueProps?: Partial; /** Gets called when a value is edited. */ onEdit?: (value: T) => void; /** Whether a separator is shown in between each section. */ separator?: boolean; } & Pick< ValueEditableUnion, | "editable" | "editing" | "labelEdit" | "onBlur" | "onChange" | "formControlProps" >; /** * AttributeGrid Component * * Uses multiple `AttributeList`s to render lists of data within a `Grid`. * * @typeParam T - The shape of a single data item. */ export const AttributeGrid = ({ attributeListProps = {}, editable, editing, decorate, errors, object, fieldsets, formControlProps = {}, fullHeight, generateTitleIds = false, title, labelEdit, separator, validate = validateForm, validators = [validateRequired], valueProps, onBlur, onChange, onEdit, ...props }: AttributeGridProps) => { const objectList = object && fieldsets?.length ? dataByFieldsets(object, fieldsets) : []; // The grid data structure: // // Built an Array of Array of tuples, each Array (1) represents a row with a max span of 12, the total span is defined // by the sum of the first key of each tuple in Array (2). const rowArray: [number, AttributeListProps][][] = [[]]; let currentRowSpan = 0; objectList.forEach((object, index) => { const fieldset = fieldsets[index]; const span = fieldset[1].span || 6; const title = fieldset[0]; const row: AttributeListProps = { ...attributeListProps, editing: editing, fields: fieldset[1].fields, object: object, title: title, colSpan: fieldset[1].colSpan || attributeListProps.colSpan, titleSpan: fieldset[1].titleSpan || attributeListProps.titleSpan, }; if (currentRowSpan + span > 12) { // Start a new row rowArray.push([]); currentRowSpan = 0; } rowArray[rowArray.length - 1].push([span, row]); currentRowSpan += span; }); /** * Gets called when an 's value is edited. */ const handleEdit = useCallback<(value: T) => void>( (value) => { const data = { ...object, ...value }; onEdit?.(data); }, [object, onEdit], ); const hasTitle = (row: [number, AttributeListProps][]) => row.some(([, props]) => props.title && props.title.trim() !== ""); return (
{title && ( {typeof title === "string" ?

{title}

: title}
)} {rowArray.map((row, rowIndex) => ( {rowIndex > 0 && separator && hasTitle(row) &&
} {row.map(([span, attributeListProps], colIndex) => ( ))}
))}
); };