JSFiddle - React, Tailwind, and code Playground

by stitot

HTML

<script src="https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/grid-pro.js"></script>

<div class="app">
  <aside class="panel">
    <div class="tabs">
      <button class="tab active" data-tab="data">Data</button>
      <button class="tab" data-tab="config">Config</button>
      <button class="tab" data-tab="theming">Theming</button>
    </div>

    <div class="panel-body">
      <!-- DATA TAB -->
      <div class="tabview active" id="tab-data">
        <div class="section">
          <div class="section-h">
            <div>Data</div>
            <span class="badge">dataTable.columns</span>
          </div>
          <div class="section-b">
            <div class="row">
              <div class="row-actions">
                <button id="addCol" type="button" class="btn">Add column</button>
                <button id="removeCol" type="button" class="btn btn-ghost">Remove last</button>
              </div>
              <div class="help">
                Edit column keys + types. Click <b>Update data</b> to rebuild the grid.
              </div>
            </div>

            <div class="row">
              <div>
                <label>Row count</label>
              </div>
              <input id="rowCount" type="number" min="1" step="1" />
              <div class="help">Number of rows to generate when you click Update data.</div>
            </div>

            <div class="data-table-wrap">
              <table class="data-table" id="dataColsTable">
                <thead>
                  <tr>
                    <th style="width:60%">Key</th>
                    <th style="width:40%">Type</th>
                  </tr>
                </thead>
                <tbody id="dataColsBody"></tbody>
              </table>
            </div>

            <div class="row">
              <label class="check">
                <input id="randomizeData" type="checkbox" />
                <span>Randomize generated...

CSS

@import url("https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/css/grid-pro.css");

:root {
  --panel-w: 420px;

  --bg: #f6f7fb;
  --panel-bg: #ffffff;
  --surface: #ffffff;
  --surface-2: #fbfbfe;

  --text: #111827;        /* slate-900 */
  --muted: #6b7280;       /* gray-500 */
  --border: #e5e7eb;      /* gray-200 */
  --border-2: #d1d5db;    /* gray-300 */

  --shadow: 0 10px 30px rgba(17, 24, 39, 0.08);
  --shadow-sm: 0 4px 14px rgba(17, 24, 39, 0.08);

  --accent: #2563eb;      /* blue-600 */
  --accent-soft: rgba(37, 99, 235, 0.10);
  --danger: #dc2626;      /* red-600 */

  --radius: 14px;
  --radius-sm: 10px;
  --font: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
}

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
}

.app {
  height: 100vh;
  display: flex;
  gap: 12px;
  padding: 12px;
  box-sizing: border-box;
}

.panel {
  width: var(--panel-w);
  min-width: var(--panel-w);
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.tabs {
  display: flex;
  gap: 8px;
  padding: 10px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.tab {
  appearance: none;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--muted);
  padding: 8px 10px;
  border-radius: 999px;
  cursor: pointer;
  font-weight: 700;
  font-size: 13px;
  transition: transform 0.05s ease, border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.tab:hover {
  border-color: var(--border-2);
  color: var(--text);
}

.tab:active {
  transform: translateY(1px);
}

.tab.active {
  background: var(--accent-soft);
  border-color: rgba(37, 99, 235, 0.35);
  color:...

JavaScript

/* Global Grid */
let grid;

/** Base data from your fiddle */
const baseOptions = {
  dataTable: {
    columns: {
      product: ["Apples", "Pears", "Plums", "Bananas"],
      weight: [100, 40, 0.5, 200],
      price: [1.5, 2.53, 5, 4.5]
    }
  }
};

const LOREM_WORDS_20 = [
  "lorem", "ipsum", "dolor", "sit", "amet",
  "consectetur", "adipiscing", "elit", "sed", "do",
  "eiusmod", "tempor", "incididunt", "ut", "labore",
  "et", "dolore", "magna", "aliqua", "enim"
];

/**
 * IMPORTANT CHANGE:
 * virtualization is intentionally UNDEFINED here to allow auto-virtualization.
 * (Tri-state UI controls it: auto => undefined, on => true, off => false)
 */
const UI_BASELINE_DEFAULTS = {
  id: "",
  caption: { text: "" },
  description: { text: "" },

  credits: { enabled: true, position: "bottom", text: "", href: "" },

  rendering: {
    theme: "hcg-theme-default",
    header: { enabled: true },
    rows: {
      // virtualization: undefined (intentionally absent)
      virtualizationThreshold: 50,
      strictHeights: false,
      bufferSize: 10,
      minVisibleRows: 2
    },
    columns: {
      resizing: { enabled: true, mode: "" }
    }
  },

  time: { timezone: "UTC" },

  columnDefaults: {
    dataType: "",
    width: "",
    sorting: { sortable: true },
    filtering: { enabled: false, inline: false },
    header: { format: "{id}" },
    cells: { format: "{value}" }
  }
};

const state = {
  uiOptions: clone(UI_BASELINE_DEFAULTS),
  selectedColumnId: "product",
  advancedPatchText: "{}",
  advancedPatchError: "",

  dataEditor: {
    columns: [
      { key: "product", type: "string" },
      { key: "weight", type: "number" },
      { key: "price", type: "number" }
    ],
    randomize: false,
    rowCount: 12
  }
};

/* ---------------------------
 * Utilities
 * --------------------------- */
function deepMerge(target, src) {
  if (!src || typeof src !== "object") return...