/v1.0

DynamicListField

DynamicListField manages an array of objects. Each item in the array is rendered as a row whose cells are defined by the slots prop. Supported slot types are text, number, longText, radio, object, autocomplete, counter, calculated, and template. Items can be added via a button, via an autocomplete picker, or both. Rows can optionally be reordered with up/down arrows.

Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | value | any[] | Yes | — | The current list of item objects. | | onChange | (items: any[]) => void | Yes | — | Called with the updated array on any mutation. | | slots | Slot[] | Yes | — | Column/cell definitions for each row (see Slot shape below). | | label | string | No | — | Label rendered in the field header. | | description | string | No | — | Helper text rendered below the field. | | canAddItems | boolean | No | true | Whether to show the "Add item" button. | | canRemoveItems | boolean | No | true | Whether to show the delete button on each row. | | isSortable | boolean | No | false | Whether to show up/down arrow buttons on each row. | | addItemMode | 'button' \| 'autocomplete' \| 'both' | No | — | Controls which add mechanisms are shown. When omitted, defaults to button if canAddItems is true. | | addItemPickerConfig | AddItemPickerConfig | No | — | Configuration for the autocomplete picker (used when addItemMode is 'autocomplete' or 'both'). See shape below. | | getDefaultItem | (ctx: { slots: Slot[]; items: any[] }) => Record<string, any> | No | — | Factory function that returns the initial value for a newly added item. Overrides the default per-slot initialization. | | breakpoint | number | No | 800 | Viewport width (px) below which the row layout switches to column mode. | | forceMobileView | boolean | No | false | Force the column layout regardless of viewport width. | | itemStyle | React.CSSProperties | No | — | Styles applied to each row container. | | containerStyle | React.CSSProperties | No | — | Styles applied to the outer FieldContainer. | | headerStyle | React.CSSProperties | No | — | Styles applied to the field header area. | | bodyStyle | React.CSSProperties | No | — | Styles applied to the field body area. | | labelStyle | React.CSSProperties | No | — | Styles applied to the label element. | | descriptionStyle | React.CSSProperties | No | — | Styles applied to the description paragraph. | | className | string | No | — | CSS class name applied to the outer container. | | id | string | No | — | id attribute forwarded. |

Slot shape

interface Slot {
  type: 'text' | 'number' | 'longText' | 'radio' | 'object' | 'autocomplete' | 'counter' | 'calculated' | 'template';
  name: string;           // Key used to read/write the value on each item object
  label?: string;         // Optional column header label
  config?: Record<string, any>; // Type-specific configuration (placeholder, options, min, max, etc.)
}

AddItemPickerConfig shape (key properties)

interface AddItemPickerConfig {
  apiBaseUrl?: string;
  path?: string;
  searchParam?: string;
  placeholder?: string;
  mapOptionToItem?: (option: any) => Record<string, any>;
  getOptionId?: (option: any) => string | number;
  getItemId?: (item: any) => string | number;
  allowDuplicates?: boolean;
  maxItems?: number;
  position?: 'top' | 'bottom';
  disabled?: boolean | ((items: any[]) => boolean);
  onItemAdded?: (item: any, option: any) => void;
  // ... additional display and search options
}

Usage

Basic list with text and number slots

import React, { useState } from 'react';
import DynamicListField from '@/components/fields/DynamicListField';

const slots = [
  { type: 'text', name: 'description', label: 'Description', config: { placeholder: 'Enter description' } },
  { type: 'number', name: 'quantity', label: 'Qty', config: { min: 1 } },
  { type: 'number', name: 'price', label: 'Price', config: { step: 0.01 } },
];

export default function Example() {
  const [items, setItems] = useState([]);

  return (
    <DynamicListField
      label="Order lines"
      slots={slots}
      value={items}
      onChange={setItems}
    />
  );
}

With autocomplete picker and sort

<DynamicListField
  label="Products"
  slots={[
    { type: 'text', name: 'name', label: 'Name' },
    { type: 'counter', name: 'qty', label: 'Qty', config: { minValue: 1 } },
  ]}
  value={lines}
  onChange={setLines}
  isSortable={true}
  addItemMode="both"
  addItemPickerConfig={{
    apiBaseUrl: 'https://api.example.com',
    path: '/v1/products',
    searchParam: 'q',
    mapOptionToItem: (opt) => ({ name: opt.name, qty: 1 }),
    getOptionId: (opt) => opt.id,
    getItemId: (item) => item.id,
  }}
/>