JSFiddle - React, Tailwind, and code Playground

by stitot

HTML

<script src="https://code.highcharts.com/dashboards/datagrid.js"></script>

<div style="margin: 1rem 0;">
  <label>
    Layout:
    <select id="layoutSelect">
      <option value="pills" selected>Pills per product</option>
      <option value="grouped">Grouped headers: editions under product</option>
    </select>
  </label>
</div>

<div id="container"></div>

CSS

@import url("https://code.highcharts.com/dashboards/css/datagrid.css");

body {
    font-family: ui-sans-serif,-apple-system,system-ui,Segoe UI,Helvetica,Apple Color Emoji,Arial,sans-serif,Segoe UI Emoji,Segoe UI Symbol;
}

h2 {
    margin: 30px 0 5px 0;
    text-transform: uppercase;
    font-weight: 500;
    font-size: 20px;
}

p {
    margin: 0 0 15px 0;
    font-size: 14px;
}

.highcharts-datagrid-table tbody tr td {
    vertical-align: top;
    line-height: 1.5em;

    &.na {
        background: rgb(248, 171, 171);
    }

    &.partial {
        background: rgb(235, 235, 143);
    }

    &.full {
        background: rgb(179, 254, 179);
    }

    div.editions span {
        display: inline-block;
        padding: 3px 4px;
        background: #959595;
        color: #ffffff;
        font-size: 0.7em;
        text-transform: uppercase;
        line-height: 1em;
        margin-right: 5px;
        border-radius:5px;
    }
}
.theme-features {
    --hcg-font-size: 14px;
    --hcg-padding: 10px;
    --hcg-horizontal-padding: 1px;
    --hcg-header-font-weight: 600;
    --hcg-header-row-border-color: #cccccc;
    --hcg-row-border-width: 1px;
    --hcg-row-border-color: #cccccc50;
}

JavaScript

// ================== Feature Matrix → Highcharts Grid (Pills vs Grouped) ==================
// - Fetch ONCE, cache payload
// - Render either layout from the SAME dataset (no refetch)
// - If a product has NO editions → it shows in BOTH layouts:
//     • Pills: as a normal product column
//     • Grouped: as a simple (non-grouped) column (i.e., not a grouped header)
// ==========================================================================================

// ---------- Utilities ----------
const escapeHtml = (s) =>
  String(s ?? "").replace(/[&<>"']/g, (ch) =>
    ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch])
  );

const colIdFor = (productName, editionName) => `${productName} | ${editionName}`;

// ---------- Core normalization ----------
function coreFromPayload(payload) {
  const data = payload && payload.result ? payload.result : payload;
  const products = Array.isArray(data?.products) ? data.products : [];
  const categories = Array.isArray(data?.categories) ? data.categories : [];

  // Keep stable product order
  const productOrder = products.map((p) => ({
    id: p._id,
    name: p.name,
    editions: Array.isArray(p.editions) ? p.editions : []
  }));

  return { categories, productOrder };
}

function indexSupport(supportedBy) {
  // Map productId → { editions: string[], comment: string }
  // (No coverage here; we compute it later using product's total editions)
  return new Map(
    (Array.isArray(supportedBy) ? supportedBy : []).map((s) => [
      s?._id,
      {
        editions: Array.isArray(s?.editions) ? s.editions : [],
        comment: typeof s?.comment === "string" ? s.comment : ""
      }
    ])
  );
}

// ---------- Unified builder (same output shape for BOTH layouts) ----------
/**
 * Build tables for the selected layout.
 * @param {'pills'|'grouped'} layout
 * @returns Array<{ name, description, dataTable: {columns}, header }>
 */
function...