/v1.0

EditableDynamicListField

EditableDynamicListField manages a dynamic list of structured objects. Each item in the list is composed of named "slots" — configurable typed columns (text, long text, number, radio, object). In read mode items are displayed as compact read-only rows. Clicking the edit icon enables full item management: adding new items, editing each slot inline, removing items, and reordering via up/down arrows. On save the entire items array is persisted via API.

Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | name | string | Yes | — | Field name sent as the key in the update payload. | | value | any[] | Yes | [] | Current array of item objects. | | slots | Slot[] | Yes | — | Column/slot definitions. Each slot has type, name, optional label, and optional config. | | updatePath | string | Yes | — | API endpoint path used to persist the change. | | label | string | No | — | Label shown in the field header. | | description | string | No | — | Helper text shown below the label. | | canAddItems | boolean | No | true | When false, the "Add item" button is hidden. | | canRemoveItems | boolean | No | true | When false, the delete button is hidden on each item. | | breakpoint | number | No | 800 | Pixel width at which the layout switches to a stacked mobile layout. | | forceMobileView | boolean | No | false | When true, always uses the stacked mobile layout regardless of screen width. | | apiBaseUrl | string | No | — | Base URL prepended to updatePath. | | useAuthToken | boolean | No | false | When true, the request includes the authorization token. | | onChange | (items: any[]) => void | No | — | Called locally when the list changes. | | onEditStart | () => void | No | — | Called when edit mode begins. | | onEditSuccess | (updatedValue: any, newFormData: any) => void | No | — | Called after successful save. | | onEditError | (error: any) => void | No | — | Called on API error; value is reverted. | | onEditCancel | () => void | No | — | Called when the user cancels; value is reverted. | | editIcon | string | No | "pencil" | Icon name for the edit button. | | saveIcon | string | No | "check" | Icon name for the save button. | | cancelIcon | string | No | "close" | Icon name for the cancel button. | | containerStyle | React.CSSProperties | No | — | Style for the outermost wrapper. | | headerStyle | React.CSSProperties | No | — | Style for the field header. | | bodyStyle | React.CSSProperties | No | — | Style for the field body. | | itemStyle | React.CSSProperties | No | — | Style applied to each item row. | | labelStyle | React.CSSProperties | No | — | Style for the <label> element. | | descriptionStyle | React.CSSProperties | No | — | Style for the description <p> element. |

Slot definition

interface Slot {
  type: 'text' | 'longText' | 'number' | 'radio' | 'object' | 'boolean' | 'array';
  name: string;       // Object key for this slot's value
  label?: string;     // Column header label
  config?: {
    placeholder?: string;
    inputStyle?: React.CSSProperties;
    label?: string;
    options?: { label: string; value: string }[]; // for 'radio'
    showIf?: string;  // Expression string to conditionally show the cell
  };
}

Usage

Basic — product variants

import React, { useState } from 'react';
import EditableDynamicListField from '@/components/editable-fields/EditableDynamicListField';

const slots = [
  { type: 'text', name: 'size', label: 'Size', config: { placeholder: 'e.g. M' } },
  { type: 'text', name: 'color', label: 'Color', config: { placeholder: 'e.g. Red' } },
  { type: 'text', name: 'sku', label: 'SKU', config: { placeholder: 'e.g. PROD-M-RED' } },
];

export default function Example() {
  const [variants, setVariants] = useState([]);

  return (
    <EditableDynamicListField
      label="Variants"
      name="variants"
      value={variants}
      slots={slots}
      updatePath="/v1/products/7"
      apiBaseUrl="https://api.example.com"
      useAuthToken
      onEditSuccess={(updated) => setVariants(updated)}
    />
  );
}

With radio slot and conditional display

const slots = [
  {
    type: 'radio',
    name: 'type',
    label: 'Type',
    config: {
      options: [
        { label: 'Fixed', value: 'fixed' },
        { label: 'Percentage', value: 'percentage' },
      ],
    },
  },
  {
    type: 'text',
    name: 'amount',
    label: 'Amount',
    config: { showIf: "type === 'fixed'" },
  },
  {
    type: 'text',
    name: 'percent',
    label: 'Percent',
    config: { showIf: "type === 'percentage'" },
  },
];