@quantumwake/terminal-ux-dashboard-components 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -13,11 +13,13 @@ var lucideReact = require('lucide-react');
13
13
  var CodeMirror = require('@uiw/react-codemirror');
14
14
  var langSql = require('@codemirror/lang-sql');
15
15
  var autocomplete = require('@codemirror/autocomplete');
16
+ var GridLayout = require('react-grid-layout');
16
17
 
17
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
19
 
19
20
  var PivotTableUI__default = /*#__PURE__*/_interopDefault(PivotTableUI);
20
21
  var CodeMirror__default = /*#__PURE__*/_interopDefault(CodeMirror);
22
+ var GridLayout__default = /*#__PURE__*/_interopDefault(GridLayout);
21
23
 
22
24
  // src/context/DashboardContext.tsx
23
25
  var DashboardContext = react.createContext(null);
@@ -46,7 +48,9 @@ function useCapabilities() {
46
48
  canAddPanel: !!c.addPanel,
47
49
  canEditPanels: !!c.removePanel,
48
50
  // Studio can recompute + persist a panel's precomputed result.
49
- canRefresh: !!c.persistPanelData
51
+ canRefresh: !!c.persistPanelData,
52
+ // Studio can drag/resize panels and persist the grid layout.
53
+ canEditLayout: !!c.persistLayout
50
54
  };
51
55
  }
52
56
 
@@ -232,6 +236,7 @@ var DEFAULT_CHART_STYLE = {
232
236
  legendHighlight: "",
233
237
  // Tick overflow handling.
234
238
  xTickRotation: -35,
239
+ yTickRotation: 0,
235
240
  tickTruncate: 0,
236
241
  // Series legend placement (charts that have one: pie, grouped bar).
237
242
  legendAnchor: "right",
@@ -307,7 +312,7 @@ var axisLegend = (style, axis, columnName, opts2 = {}) => {
307
312
  out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
308
313
  out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
309
314
  }
310
- if (isX) out.tickRotation = s.xTickRotation;
315
+ out.tickRotation = isX ? s.xTickRotation : s.yTickRotation;
311
316
  if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
312
317
  else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
313
318
  return out;
@@ -616,11 +621,17 @@ function PivotView({ records }) {
616
621
  )
617
622
  ] });
618
623
  }
619
- function Row({ label, children }) {
624
+ function Section({ title, children }) {
620
625
  const { theme } = useDashboard();
621
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
622
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs ${theme.font} text-midnight-text-muted`, children: label }),
623
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children })
626
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `py-3 border-t ${theme.border} first:border-t-0 first:pt-0`, children: [
627
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] uppercase tracking-wider text-midnight-text-muted/70 font-mono mb-2", children: title }),
628
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children })
629
+ ] });
630
+ }
631
+ function Row({ label, children }) {
632
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[88px_1fr] items-center gap-2 min-h-[30px]", children: [
633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
634
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end", children })
624
635
  ] });
625
636
  }
626
637
  function ColorControl({ value, onChange }) {
@@ -631,23 +642,29 @@ function ColorControl({ value, onChange }) {
631
642
  type: "color",
632
643
  value: /^#/.test(value) ? value : "#94a3b8",
633
644
  onChange: (e) => onChange(e.target.value),
634
- className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer`
645
+ className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer p-0`
635
646
  }
636
647
  );
637
648
  }
638
- function NumberControl({ value, onChange, min, max }) {
639
- const { theme } = useDashboard();
640
- return /* @__PURE__ */ jsxRuntime.jsx(
641
- "input",
642
- {
643
- type: "number",
649
+ function SliderControl({ value, onChange, min, max, step = 1, unit = "" }) {
650
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
651
+ /* @__PURE__ */ jsxRuntime.jsx(
652
+ "input",
653
+ {
654
+ type: "range",
655
+ min,
656
+ max,
657
+ step,
658
+ value,
659
+ onChange: (e) => onChange(Number(e.target.value)),
660
+ className: "flex-1 h-1 accent-midnight-accent cursor-pointer"
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "w-10 shrink-0 text-right text-[11px] font-mono tabular-nums text-midnight-text-body", children: [
644
664
  value,
645
- min,
646
- max,
647
- onChange: (e) => onChange(Number(e.target.value)),
648
- className: `w-20 px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`
649
- }
650
- );
665
+ unit
666
+ ] })
667
+ ] });
651
668
  }
652
669
  function TextControl({ value, onChange, placeholder }) {
653
670
  const { theme } = useDashboard();
@@ -658,78 +675,79 @@ function TextControl({ value, onChange, placeholder }) {
658
675
  value,
659
676
  placeholder,
660
677
  onChange: (e) => onChange(e.target.value),
661
- className: `w-28 px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`
678
+ className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`
662
679
  }
663
680
  );
664
681
  }
665
- function Toggle({ value, onChange }) {
682
+ function Switch({ value, onChange }) {
666
683
  return /* @__PURE__ */ jsxRuntime.jsx(
667
- "input",
684
+ "button",
668
685
  {
669
- type: "checkbox",
670
- checked: value,
671
- onChange: (e) => onChange(e.target.checked),
672
- className: "w-4 h-4 accent-midnight-accent cursor-pointer"
686
+ type: "button",
687
+ role: "switch",
688
+ "aria-checked": value,
689
+ onClick: () => onChange(!value),
690
+ className: `relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors ${value ? "bg-midnight-accent" : "bg-midnight-border"}`,
691
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${value ? "translate-x-3.5" : "translate-x-0.5"}` })
673
692
  }
674
693
  );
675
694
  }
676
695
  function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
677
696
  const on = !!value;
678
697
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
679
- /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: on, onChange: (v) => onChange(v ? fallback : "") }),
680
- on && /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value, onChange })
698
+ on && /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value, onChange }),
699
+ /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: on, onChange: (v) => onChange(v ? fallback : "") })
681
700
  ] });
682
701
  }
683
702
  function Choice({ value, options, onChange }) {
684
703
  const { theme } = useDashboard();
685
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-28", children: /* @__PURE__ */ jsxRuntime.jsx(
704
+ return /* @__PURE__ */ jsxRuntime.jsx(
686
705
  "select",
687
706
  {
688
707
  value,
689
708
  onChange: (e) => onChange(e.target.value),
690
- className: `w-full px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`,
709
+ className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`,
691
710
  children: options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o, children: o }, o))
692
711
  }
693
- ) });
694
- }
695
- function Group({ children, last }) {
696
- const { theme } = useDashboard();
697
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `space-y-2 pb-3 ${last ? "" : `mb-3 border-b ${theme.border}`}`, children });
712
+ );
698
713
  }
699
714
  function ChartStyleControls({ style, onChange }) {
700
715
  const s = withStyleDefaults(style);
701
716
  const set = (key, value) => onChange({ ...s, [key]: value });
702
717
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
703
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
718
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Canvas", children: [
704
719
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
705
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
706
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Grid color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
707
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.fontSize, min: 6, max: 24, onChange: (v) => set("fontSize", v) }) })
720
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
721
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
722
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
708
723
  ] }),
709
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
710
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X axis title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
711
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show X title", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
712
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y axis title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
713
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show Y title", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
714
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
715
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title bold", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
716
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title highlight", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) }),
717
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
718
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
719
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
720
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
724
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Axis titles", children: [
725
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
726
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
727
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
728
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
729
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
730
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
731
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
721
732
  ] }),
722
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
723
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X tick angle", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.xTickRotation, min: -90, max: 90, onChange: (v) => set("xTickRotation", v) }) }),
724
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate ticks", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
733
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Axis placement", children: [
734
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
735
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
736
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
737
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
725
738
  ] }),
726
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { last: true, children: [
727
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Series legend", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }),
728
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title align", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
729
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title bold", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
730
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title color", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
731
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title bg", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
732
- ] })
739
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Ticks", children: [
740
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
741
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
742
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsxRuntime.jsx(SliderControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
743
+ ] }),
744
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Panel title", children: [
745
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Align", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
749
+ ] }),
750
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Position", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
733
751
  ] });
734
752
  }
735
753
  var AGGREGATES = [
@@ -1999,57 +2017,112 @@ function PanelContent({ panel, records, columns }) {
1999
2017
  ] });
2000
2018
  }
2001
2019
  }
2002
- var ROW_HEIGHT = 180;
2020
+ var GRID_COLS = 12;
2021
+ var ROW_HEIGHT = 80;
2022
+ var GRID_MARGIN = 10;
2023
+ var DRAG_HANDLE = "panel-drag-handle";
2024
+ var RGL_CSS = `
2025
+ .react-grid-layout { position: relative; transition: height 200ms ease; }
2026
+ .react-grid-item { transition: all 200ms ease; transition-property: left, top, width, height; box-sizing: border-box; }
2027
+ .react-grid-item.cssTransforms { transition-property: transform, width, height; }
2028
+ .react-grid-item.resizing { transition: none; z-index: 3; will-change: width, height; }
2029
+ .react-grid-item.react-draggable-dragging { transition: none; z-index: 3; will-change: transform; }
2030
+ .react-grid-item.react-grid-placeholder { background: rgba(99,102,241,0.18); border: 1px dashed #6366f1; border-radius: 2px; transition-duration: 100ms; z-index: 2; user-select: none; }
2031
+ .react-grid-item > .react-resizable-handle { position: absolute; width: 18px; height: 18px; bottom: 0; right: 0; cursor: se-resize; }
2032
+ .react-grid-item > .react-resizable-handle::after { content: ''; position: absolute; right: 4px; bottom: 4px; width: 6px; height: 6px; border-right: 2px solid rgba(148,163,184,0.7); border-bottom: 2px solid rgba(148,163,184,0.7); }
2033
+ .${DRAG_HANDLE} { cursor: grab; }
2034
+ .react-grid-item.react-draggable-dragging .${DRAG_HANDLE} { cursor: grabbing; }
2035
+ `;
2036
+ var rglCssInjected = false;
2037
+ function useInjectRglCss() {
2038
+ react.useEffect(() => {
2039
+ if (rglCssInjected || typeof document === "undefined") return;
2040
+ const el = document.createElement("style");
2041
+ el.setAttribute("data-rgl", "dashboard-renderer");
2042
+ el.textContent = RGL_CSS;
2043
+ document.head.appendChild(el);
2044
+ rglCssInjected = true;
2045
+ }, []);
2046
+ }
2047
+ var GridLayoutWithWidth = GridLayout.WidthProvider(GridLayout__default.default);
2048
+ function buildLayout(panels) {
2049
+ let cx = 0, cy = 0, rowH = 0;
2050
+ return panels.map((p) => {
2051
+ const w = Math.min(Math.max(p.width || 6, 1), GRID_COLS);
2052
+ const h = Math.max(p.height || 4, 1);
2053
+ if (typeof p.x === "number" && typeof p.y === "number") {
2054
+ return { i: p.id, x: p.x, y: p.y, w, h, minW: 2, minH: 2 };
2055
+ }
2056
+ if (cx + w > GRID_COLS) {
2057
+ cx = 0;
2058
+ cy += rowH;
2059
+ rowH = 0;
2060
+ }
2061
+ const item = { i: p.id, x: cx, y: cy, w, h, minW: 2, minH: 2 };
2062
+ cx += w;
2063
+ rowH = Math.max(rowH, h);
2064
+ return item;
2065
+ });
2066
+ }
2003
2067
  function DashboardRenderer({ dashboard, records, columns }) {
2004
- const { theme, removePanel } = useDashboard();
2005
- const { canEditPanels } = useCapabilities();
2068
+ const { theme, removePanel, persistLayout } = useDashboard();
2069
+ const { canEditPanels, canEditLayout } = useCapabilities();
2070
+ useInjectRglCss();
2071
+ const panels = dashboard?.panels ?? [];
2072
+ const layout = react.useMemo(() => buildLayout(panels), [panels]);
2073
+ const onLayoutChange = react.useCallback((next) => {
2074
+ if (!persistLayout) return;
2075
+ persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
2076
+ }, [persistLayout]);
2006
2077
  if (!dashboard) return null;
2007
2078
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2008
2079
  dashboard.insights && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2009
2080
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2010
2081
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2011
2082
  ] }),
2012
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-3", children: dashboard.panels?.map((panel) => {
2013
- const colSpan = Math.min(Math.max(panel.width || 6, 1), 12);
2014
- const rowSpan = Math.min(Math.max(panel.height || 2, 1), 4);
2015
- return /* @__PURE__ */ jsxRuntime.jsxs(
2016
- "div",
2017
- {
2018
- className: `border ${theme.border} bg-midnight-surface flex flex-col`,
2019
- style: {
2020
- gridColumn: `span ${colSpan}`,
2021
- minHeight: `${rowSpan * ROW_HEIGHT}px`
2022
- },
2023
- children: [
2024
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated`, children: [
2025
- /* @__PURE__ */ jsxRuntime.jsx(
2026
- "span",
2027
- {
2028
- className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2029
- style: {
2030
- textAlign: panel.config?.style?.titleAlign || "left",
2031
- fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2032
- background: panel.config?.style?.titleBackground || void 0,
2033
- color: panel.config?.style?.titleColor || void 0
2034
- },
2035
- children: panel.title || panel.type
2036
- }
2037
- ),
2038
- canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
2039
- "button",
2040
- {
2041
- onClick: () => removePanel(panel.id),
2042
- className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2043
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
2044
- }
2045
- )
2046
- ] }),
2047
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ViewLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PanelContent, { panel, records, columns }) }) })
2048
- ]
2049
- },
2050
- panel.id
2051
- );
2052
- }) })
2083
+ /* @__PURE__ */ jsxRuntime.jsx(
2084
+ GridLayoutWithWidth,
2085
+ {
2086
+ className: "layout",
2087
+ layout,
2088
+ cols: GRID_COLS,
2089
+ rowHeight: ROW_HEIGHT,
2090
+ margin: [GRID_MARGIN, GRID_MARGIN],
2091
+ isDraggable: canEditLayout,
2092
+ isResizable: canEditLayout,
2093
+ draggableHandle: `.${DRAG_HANDLE}`,
2094
+ onDragStop: onLayoutChange,
2095
+ onResizeStop: onLayoutChange,
2096
+ compactType: canEditLayout ? "vertical" : null,
2097
+ children: panels.map((panel) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden`, children: [
2098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${canEditLayout ? DRAG_HANDLE : ""}`, children: [
2099
+ /* @__PURE__ */ jsxRuntime.jsx(
2100
+ "span",
2101
+ {
2102
+ className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2103
+ style: {
2104
+ textAlign: panel.config?.style?.titleAlign || "left",
2105
+ fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2106
+ background: panel.config?.style?.titleBackground || void 0,
2107
+ color: panel.config?.style?.titleColor || void 0
2108
+ },
2109
+ children: panel.title || panel.type
2110
+ }
2111
+ ),
2112
+ canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
2113
+ "button",
2114
+ {
2115
+ onMouseDown: (e) => e.stopPropagation(),
2116
+ onClick: () => removePanel(panel.id),
2117
+ className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2118
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
2119
+ }
2120
+ )
2121
+ ] }),
2122
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ViewLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PanelContent, { panel, records, columns }) }) })
2123
+ ] }, panel.id))
2124
+ }
2125
+ )
2053
2126
  ] });
2054
2127
  }
2055
2128
  function ProfileSummary({ profile, theme }) {