/v1.0

EditableDynamicListField

EditableDynamicListField gestiona una lista dinámica de objetos estructurados. Cada ítem de la lista está compuesto por "slots" nombrados — columnas tipadas configurables (texto, texto largo, número, radio, objeto). En modo lectura los ítems se muestran como filas compactas de solo lectura. Al hacer clic en el ícono de edición se habilita la gestión completa: agregar nuevos ítems, editar cada slot inline, eliminar ítems y reordenar con flechas arriba/abajo. Al guardar, el array completo de ítems se persiste mediante la API.

Props

| Prop | Tipo | Requerido | Por defecto | Descripción | |------|------|-----------|-------------|-------------| | name | string | Sí | — | Nombre del campo enviado como clave en el payload de actualización. | | value | any[] | Sí | [] | Array actual de objetos de ítems. | | slots | Slot[] | Sí | — | Definiciones de columna/slot. Cada slot tiene type, name, label opcional y config opcional. | | updatePath | string | Sí | — | Ruta del endpoint API para persistir el cambio. | | label | string | No | — | Etiqueta mostrada en el encabezado del campo. | | description | string | No | — | Texto de ayuda mostrado debajo de la etiqueta. | | canAddItems | boolean | No | true | Cuando es false, el botón "Agregar ítem" se oculta. | | canRemoveItems | boolean | No | true | Cuando es false, el botón de eliminar se oculta en cada ítem. | | breakpoint | number | No | 800 | Ancho en píxeles en el que el layout cambia a vista móvil apilada. | | forceMobileView | boolean | No | false | Cuando es true, siempre usa el layout apilado independientemente del ancho de pantalla. | | apiBaseUrl | string | No | — | URL base que se antepone a updatePath. | | useAuthToken | boolean | No | false | Cuando es true, el request incluye el token de autorización. | | onChange | (items: any[]) => void | No | — | Llamado localmente cuando la lista cambia. | | onEditStart | () => void | No | — | Llamado cuando comienza el modo edición. | | onEditSuccess | (updatedValue: any, newFormData: any) => void | No | — | Llamado tras un guardado exitoso. | | onEditError | (error: any) => void | No | — | Llamado al fallar el request; el valor se revierte. | | onEditCancel | () => void | No | — | Llamado al cancelar; el valor se revierte. | | editIcon | string | No | "pencil" | Nombre del ícono para el botón de edición. | | saveIcon | string | No | "check" | Nombre del ícono para el botón de guardar. | | cancelIcon | string | No | "close" | Nombre del ícono para el botón de cancelar. | | containerStyle | React.CSSProperties | No | — | Estilo del contenedor externo. | | headerStyle | React.CSSProperties | No | — | Estilo del encabezado del campo. | | bodyStyle | React.CSSProperties | No | — | Estilo del cuerpo del campo. | | itemStyle | React.CSSProperties | No | — | Estilo aplicado a cada fila de ítem. | | labelStyle | React.CSSProperties | No | — | Estilo del elemento <label>. | | descriptionStyle | React.CSSProperties | No | — | Estilo del <p> de descripción. |

Definición de Slot

interface Slot {
  type: 'text' | 'longText' | 'number' | 'radio' | 'object' | 'boolean' | 'array';
  name: string;       // Clave del objeto para el valor de este slot
  label?: string;     // Encabezado de columna
  config?: {
    placeholder?: string;
    inputStyle?: React.CSSProperties;
    label?: string;
    options?: { label: string; value: string }[]; // para tipo 'radio'
    showIf?: string;  // Expresión para mostrar el campo condicionalmente
  };
}

Uso

Básico — variantes de producto

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)}
    />
  );
}

Con slot radio y visualización condicional

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'" },
  },
];