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

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
@@ -9,6 +9,7 @@ var scatterplot = require('@nivo/scatterplot');
9
9
  var heatmap = require('@nivo/heatmap');
10
10
  var PivotTableUI = require('react-pivottable/PivotTableUI');
11
11
  require('react-pivottable/pivottable.css');
12
+ var terminalUxComponents = require('@quantumwake/terminal-ux-components');
12
13
  var lucideReact = require('lucide-react');
13
14
  var CodeMirror = require('@uiw/react-codemirror');
14
15
  var langSql = require('@codemirror/lang-sql');
@@ -235,9 +236,15 @@ var DEFAULT_CHART_STYLE = {
235
236
  legendBold: false,
236
237
  legendHighlight: "",
237
238
  // Tick overflow handling.
238
- xTickRotation: -35,
239
+ xTickRotation: 0,
239
240
  yTickRotation: 0,
240
241
  tickTruncate: 0,
242
+ tickWrap: true,
243
+ tickWrapWidth: 14,
244
+ // Table cells.
245
+ cellWrap: true,
246
+ cellPrecision: 2,
247
+ cellTruncate: 0,
241
248
  // Series legend placement (charts that have one: pie, grouped bar).
242
249
  legendAnchor: "right",
243
250
  // Panel title (rendered by DashboardRenderer's panel header).
@@ -362,6 +369,78 @@ function MetricView({ records, config, value: presetValue }) {
362
369
  function InsightView({ config }) {
363
370
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full p-4 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-midnight-text-body leading-relaxed whitespace-pre-wrap", children: config.text }) });
364
371
  }
372
+ function formatVal(value, s, numeric) {
373
+ let str = numeric ? Number(value).toLocaleString() : String(value);
374
+ if (s.tickTruncate > 0 && str.length > s.tickTruncate) str = `${str.slice(0, s.tickTruncate)}\u2026`;
375
+ return str;
376
+ }
377
+ function wrapText(str, width) {
378
+ if (width <= 0 || str.length <= width) return [str];
379
+ const words = str.split(/\s+/);
380
+ const lines = [];
381
+ let cur = "";
382
+ const push = (t) => {
383
+ if (t) lines.push(t);
384
+ };
385
+ for (const w of words) {
386
+ if (w.length > width) {
387
+ push(cur);
388
+ cur = "";
389
+ for (let i = 0; i < w.length; i += width) lines.push(w.slice(i, i + width));
390
+ continue;
391
+ }
392
+ if (!cur) cur = w;
393
+ else if (cur.length + 1 + w.length <= width) cur += ` ${w}`;
394
+ else {
395
+ push(cur);
396
+ cur = w;
397
+ }
398
+ }
399
+ push(cur);
400
+ if (lines.length > 4) {
401
+ const kept = lines.slice(0, 4);
402
+ kept[3] = `${kept[3].slice(0, Math.max(0, width - 1))}\u2026`;
403
+ return kept;
404
+ }
405
+ return lines;
406
+ }
407
+ function makeAxis(style, axis, columnName, opts2 = {}) {
408
+ const s = withStyleDefaults(style);
409
+ const isX = axis === "x";
410
+ const numeric = !!opts2.numeric;
411
+ const show = isX ? s.showXLegend : s.showYLegend;
412
+ const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
413
+ const rotate = isX ? s.xTickRotation : s.yTickRotation;
414
+ const out = { tickSize: 5, tickPadding: 5 };
415
+ if (show && label) {
416
+ out.legend = label;
417
+ out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
418
+ out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
419
+ }
420
+ if (s.tickWrap) {
421
+ out.renderTick = (tick) => {
422
+ const lines = wrapText(formatVal(tick.value, s, numeric), s.tickWrapWidth);
423
+ const firstDy = isX ? "0" : `${-((lines.length - 1) * 0.55)}em`;
424
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${tick.x},${tick.y})`, children: [
425
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x2: isX ? 0 : -5, y2: isX ? 5 : 0, style: { stroke: s.textColor, strokeWidth: 1, opacity: 0.3 } }),
426
+ /* @__PURE__ */ jsxRuntime.jsx(
427
+ "text",
428
+ {
429
+ transform: `translate(${tick.textX},${tick.textY}) rotate(${rotate})`,
430
+ textAnchor: tick.textAnchor,
431
+ dominantBaseline: tick.textBaseline,
432
+ style: { fill: s.textColor, fontSize: s.fontSize },
433
+ children: lines.map((ln, i) => /* @__PURE__ */ jsxRuntime.jsx("tspan", { x: 0, dy: i === 0 ? firstDy : "1.1em", children: ln }, i))
434
+ }
435
+ )
436
+ ] });
437
+ };
438
+ } else {
439
+ out.tickRotation = rotate;
440
+ out.format = (v) => formatVal(v, s, numeric);
441
+ }
442
+ return out;
443
+ }
365
444
  function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, style }) {
366
445
  const computed = react.useMemo(() => {
367
446
  if (presetData || !records) return [];
@@ -369,7 +448,7 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
369
448
  return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
370
449
  }, [records, groupColumn, valueColumn, aggFn, presetData]);
371
450
  const data = presetData || computed;
372
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
451
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
373
452
  bar.ResponsiveBar,
374
453
  {
375
454
  data,
@@ -379,8 +458,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
379
458
  padding: 0.3,
380
459
  colors: ["rgba(74, 222, 128, 0.8)"],
381
460
  borderColor: { from: "color", modifiers: [["darker", 1.6]] },
382
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
383
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
461
+ axisBottom: makeAxis(style, "x", groupColumn),
462
+ axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
384
463
  labelSkipWidth: 12,
385
464
  labelSkipHeight: 12,
386
465
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -401,7 +480,7 @@ function PieView({ records, groupColumn, data: presetData, style }) {
401
480
  }, [records, groupColumn, presetData]);
402
481
  const data = presetData || computed;
403
482
  const legend = legendConfig(style);
404
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
483
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
405
484
  pie.ResponsivePie,
406
485
  {
407
486
  data,
@@ -437,7 +516,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
437
516
  }];
438
517
  }, [records, xColumn, yColumn, presetData]);
439
518
  const data = presetData || computed;
440
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
519
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
441
520
  line.ResponsiveLine,
442
521
  {
443
522
  data,
@@ -453,8 +532,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
453
532
  pointBorderWidth: 2,
454
533
  pointBorderColor: { from: "serieColor" },
455
534
  enableGridX: false,
456
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
457
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
535
+ axisBottom: makeAxis(style, "x", xColumn),
536
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
458
537
  useMesh: true,
459
538
  theme: buildNivoTheme(style)
460
539
  }
@@ -467,7 +546,7 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
467
546
  return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
468
547
  }, [records, xColumn, yColumn, presetData]);
469
548
  const data = presetData || computed;
470
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
549
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
471
550
  scatterplot.ResponsiveScatterPlot,
472
551
  {
473
552
  data,
@@ -476,8 +555,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
476
555
  yScale: { type: "linear", min: "auto", max: "auto" },
477
556
  colors: ["rgba(167, 139, 250, 0.7)"],
478
557
  nodeSize: 6,
479
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
480
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
558
+ axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
559
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
481
560
  useMesh: true,
482
561
  theme: buildNivoTheme(style)
483
562
  }
@@ -571,7 +650,7 @@ function HeatmapView({
571
650
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
572
651
  }
573
652
  const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
574
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
653
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
575
654
  heatmap.ResponsiveHeatMap,
576
655
  {
577
656
  data,
@@ -622,73 +701,25 @@ function PivotView({ records }) {
622
701
  ] });
623
702
  }
624
703
  function Section({ title, children }) {
625
- const { theme } = useDashboard();
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 })
704
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-midnight-border font-mono", style: { paddingTop: 12, paddingBottom: 12 }, children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "uppercase tracking-wider text-midnight-text-subdued", style: { fontSize: 10, marginBottom: 8 }, children: title }),
706
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", rowGap: 6 }, children })
629
707
  ] });
630
708
  }
631
709
  function Row({ label, children }) {
632
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[88px_1fr] items-center gap-2 min-h-[30px]", children: [
710
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gridTemplateColumns: "84px minmax(0, 1fr)", alignItems: "center", columnGap: 8, height: 32 }, children: [
633
711
  /* @__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 })
712
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children })
635
713
  ] });
636
714
  }
637
715
  function ColorControl({ value, onChange }) {
638
- const { theme } = useDashboard();
639
716
  return /* @__PURE__ */ jsxRuntime.jsx(
640
717
  "input",
641
718
  {
642
719
  type: "color",
643
720
  value: /^#/.test(value) ? value : "#94a3b8",
644
721
  onChange: (e) => onChange(e.target.value),
645
- className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer p-0`
646
- }
647
- );
648
- }
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: [
664
- value,
665
- unit
666
- ] })
667
- ] });
668
- }
669
- function TextControl({ value, onChange, placeholder }) {
670
- const { theme } = useDashboard();
671
- return /* @__PURE__ */ jsxRuntime.jsx(
672
- "input",
673
- {
674
- type: "text",
675
- value,
676
- placeholder,
677
- onChange: (e) => onChange(e.target.value),
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`
679
- }
680
- );
681
- }
682
- function Switch({ value, onChange }) {
683
- return /* @__PURE__ */ jsxRuntime.jsx(
684
- "button",
685
- {
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"}` })
722
+ className: "w-9 h-6 bg-transparent border border-midnight-border cursor-pointer p-0"
692
723
  }
693
724
  );
694
725
  }
@@ -696,21 +727,9 @@ function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
696
727
  const on = !!value;
697
728
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
698
729
  on && /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value, onChange }),
699
- /* @__PURE__ */ jsxRuntime.jsx(Switch, { value: on, onChange: (v) => onChange(v ? fallback : "") })
730
+ /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: on, onChange: (v) => onChange(v ? fallback : "") })
700
731
  ] });
701
732
  }
702
- function Choice({ value, options, onChange }) {
703
- const { theme } = useDashboard();
704
- return /* @__PURE__ */ jsxRuntime.jsx(
705
- "select",
706
- {
707
- value,
708
- onChange: (e) => onChange(e.target.value),
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`,
710
- children: options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o, children: o }, o))
711
- }
712
- );
713
- }
714
733
  function ChartStyleControls({ style, onChange }) {
715
734
  const s = withStyleDefaults(style);
716
735
  const set = (key, value) => onChange({ ...s, [key]: value });
@@ -719,35 +738,42 @@ function ChartStyleControls({ style, onChange }) {
719
738
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
720
739
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
721
740
  /* @__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) }) })
741
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
723
742
  ] }),
724
743
  /* @__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) }) }),
744
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalInput, { size: "small", value: s.xAxisLabel, placeholder: "(column)", onChange: (e) => set("xAxisLabel", e.target.value) }) }),
745
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalInput, { size: "small", value: s.yAxisLabel, placeholder: "(column)", onChange: (e) => set("yAxisLabel", e.target.value) }) }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
729
748
  /* @__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) }) }),
749
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
731
750
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
732
751
  ] }),
733
752
  /* @__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) }) })
753
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
754
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
756
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
738
757
  ] }),
739
758
  /* @__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) }) })
759
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.tickWrap, onChange: (v) => set("tickWrap", v) }) }),
760
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap width", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.tickWrapWidth, min: 4, max: 40, onChange: (v) => set("tickWrapWidth", v) }) }),
761
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
762
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
763
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
764
+ ] }),
765
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Table cells", children: [
766
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.cellWrap, onChange: (v) => set("cellWrap", v) }) }),
767
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Decimals", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.cellPrecision, min: 0, max: 8, onChange: (v) => set("cellPrecision", v) }) }),
768
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.cellTruncate, min: 0, max: 80, onChange: (v) => set("cellTruncate", v) }) })
743
769
  ] }),
744
770
  /* @__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) }) }),
771
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Align", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
772
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
747
773
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
748
774
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
749
775
  ] }),
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) }) }) })
776
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Position", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
751
777
  ] });
752
778
  }
753
779
  var AGGREGATES = [
@@ -1355,7 +1381,7 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1355
1381
  const keys = presetKeys || yFields.map((yf) => `${yf.agg}(${yf.name})`);
1356
1382
  const colors = ["rgba(74,222,128,0.8)", "rgba(96,165,250,0.8)", "rgba(251,146,60,0.8)", "rgba(167,139,250,0.8)", "rgba(248,113,113,0.8)"];
1357
1383
  const legend = legendConfig(style);
1358
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
1384
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
1359
1385
  bar.ResponsiveBar,
1360
1386
  {
1361
1387
  data,
@@ -1365,8 +1391,8 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1365
1391
  margin: { top: 20, right: 120, bottom: 60, left: 60 },
1366
1392
  padding: 0.3,
1367
1393
  colors: colors.slice(0, keys.length),
1368
- axisBottom: { tickSize: 5, tickPadding: 5, tickRotation: -35 },
1369
- axisLeft: { tickSize: 5, tickPadding: 5, format: (v) => Number(v).toLocaleString() },
1394
+ axisBottom: makeAxis(style, "x", xFields[0]?.name || ""),
1395
+ axisLeft: makeAxis(style, "y", "", { numeric: true }),
1370
1396
  labelSkipWidth: 12,
1371
1397
  labelSkipHeight: 12,
1372
1398
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -1472,6 +1498,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1472
1498
  const [style, setStyle] = react.useState(DEFAULT_CHART_STYLE);
1473
1499
  const [showStyle, setShowStyle] = react.useState(false);
1474
1500
  const [showFields, setShowFields] = react.useState(false);
1501
+ const [fillContainer, setFillContainer] = react.useState(false);
1475
1502
  const [sqlRows, setSqlRows] = react.useState(null);
1476
1503
  const [sqlLoading, setSqlLoading] = react.useState(false);
1477
1504
  const [sqlError, setSqlError] = react.useState(null);
@@ -1586,12 +1613,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1586
1613
  }
1587
1614
  }
1588
1615
  config.style = style;
1616
+ config.fill = fillContainer;
1589
1617
  onSave({
1590
1618
  title: title || `${chartType} chart`,
1591
1619
  type: chartType === "grouped-bar" ? "bar" : chartType,
1592
1620
  config,
1593
- width: 6,
1594
- height: 2
1621
+ width: fillContainer ? 12 : 6,
1622
+ height: fillContainer ? 8 : 2
1595
1623
  });
1596
1624
  };
1597
1625
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full", children: [
@@ -1747,6 +1775,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1747
1775
  className: "w-full mb-2 px-2 py-1 text-xs font-mono bg-midnight-surface border border-midnight-border text-midnight-text-body outline-none focus:border-midnight-accent transition-colors"
1748
1776
  }
1749
1777
  ),
1778
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1779
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", title: "Span the full dashboard width and height", children: "Maximize size" }),
1780
+ /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: fillContainer, onChange: setFillContainer })
1781
+ ] }),
1750
1782
  /* @__PURE__ */ jsxRuntime.jsx(
1751
1783
  "button",
1752
1784
  {
@@ -1843,14 +1875,26 @@ function panelSql(type, config) {
1843
1875
  if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
1844
1876
  return "";
1845
1877
  }
1846
- function DataTable({ rows, columns }) {
1878
+ function formatCell(value, s) {
1879
+ if (value == null) return "";
1880
+ if (typeof value === "object") return JSON.stringify(value);
1881
+ let str;
1882
+ const n = typeof value === "number" ? value : Number(value);
1883
+ if (value !== "" && typeof value !== "boolean" && Number.isFinite(n)) {
1884
+ str = Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { minimumFractionDigits: s.cellPrecision, maximumFractionDigits: s.cellPrecision });
1885
+ } else {
1886
+ str = String(value);
1887
+ }
1888
+ if (s.cellTruncate > 0 && str.length > s.cellTruncate) str = `${str.slice(0, s.cellTruncate)}\u2026`;
1889
+ return str;
1890
+ }
1891
+ function DataTable({ rows, columns, style }) {
1892
+ const s = withStyleDefaults(style);
1847
1893
  const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
1894
+ const cellCls = s.cellWrap ? "px-2 py-1 text-midnight-text-body align-top whitespace-normal break-words max-w-[280px]" : "px-2 py-1 text-midnight-text-body truncate max-w-[200px]";
1848
1895
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
1849
1896
  /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 bg-midnight-elevated", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: colNames.map((n) => /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-2 py-1 text-left text-midnight-text-muted font-mono border-b border-midnight-border", children: n }, n)) }) }),
1850
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
1851
- const cellValue = r[n];
1852
- return /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-2 py-1 text-midnight-text-body truncate max-w-[200px]", children: cellValue == null ? "" : typeof cellValue === "object" ? JSON.stringify(cellValue) : String(cellValue) }, n);
1853
- }) }, i)) })
1897
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: cellCls, children: formatCell(r[n], s) }, n)) }, i)) })
1854
1898
  ] }) });
1855
1899
  }
1856
1900
  function SqlPanel({ panel }) {
@@ -1945,7 +1989,7 @@ function SqlPanel({ panel }) {
1945
1989
  const v = first ? Object.values(first)[0] : 0;
1946
1990
  chart = /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1947
1991
  } else if (chartType === "table") {
1948
- chart = /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows, columns: config.columns });
1992
+ chart = /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows, columns: config.columns, style: config.style });
1949
1993
  } else {
1950
1994
  const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1951
1995
  switch (chartType) {
@@ -2009,7 +2053,7 @@ function PanelContent({ panel, records, columns }) {
2009
2053
  case "insight":
2010
2054
  return /* @__PURE__ */ jsxRuntime.jsx(InsightView, { config: { text: config.text } });
2011
2055
  case "table":
2012
- return /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
2056
+ return /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name), style: config.style });
2013
2057
  default:
2014
2058
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
2015
2059
  "Unknown panel type: ",
@@ -2048,10 +2092,11 @@ var GridLayoutWithWidth = GridLayout.WidthProvider(GridLayout__default.default);
2048
2092
  function buildLayout(panels) {
2049
2093
  let cx = 0, cy = 0, rowH = 0;
2050
2094
  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);
2095
+ const fill = !!p.config?.fill;
2096
+ const w = fill ? GRID_COLS : Math.min(Math.max(p.width || 6, 1), GRID_COLS);
2097
+ const h = fill ? Math.max(p.height || 8, 6) : Math.max(p.height || 4, 1);
2053
2098
  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 };
2099
+ return { i: p.id, x: fill ? 0 : p.x, y: p.y, w, h, minW: 2, minH: 2 };
2055
2100
  }
2056
2101
  if (cx + w > GRID_COLS) {
2057
2102
  cx = 0;
@@ -2075,11 +2120,45 @@ function DashboardRenderer({ dashboard, records, columns }) {
2075
2120
  persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
2076
2121
  }, [persistLayout]);
2077
2122
  if (!dashboard) return null;
2078
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2079
- dashboard.insights && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2080
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2081
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2123
+ const panelFrame = (panel, draggable) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden h-full`, children: [
2124
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${draggable ? DRAG_HANDLE : ""}`, children: [
2125
+ /* @__PURE__ */ jsxRuntime.jsx(
2126
+ "span",
2127
+ {
2128
+ className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2129
+ style: {
2130
+ textAlign: panel.config?.style?.titleAlign || "left",
2131
+ fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2132
+ background: panel.config?.style?.titleBackground || void 0,
2133
+ color: panel.config?.style?.titleColor || void 0
2134
+ },
2135
+ children: panel.title || panel.type
2136
+ }
2137
+ ),
2138
+ canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
2139
+ "button",
2140
+ {
2141
+ onMouseDown: (e) => e.stopPropagation(),
2142
+ onClick: () => removePanel(panel.id),
2143
+ className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2144
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
2145
+ }
2146
+ )
2082
2147
  ] }),
2148
+ /* @__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 }) }) })
2149
+ ] }, panel.id);
2150
+ const insightsBanner = dashboard.insights ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2151
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2152
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2153
+ ] }) : null;
2154
+ if (panels.length === 1) {
2155
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2 h-full min-h-[60vh]", children: [
2156
+ insightsBanner,
2157
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: panelFrame(panels[0], false) })
2158
+ ] });
2159
+ }
2160
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2161
+ insightsBanner,
2083
2162
  /* @__PURE__ */ jsxRuntime.jsx(
2084
2163
  GridLayoutWithWidth,
2085
2164
  {
@@ -2094,33 +2173,7 @@ function DashboardRenderer({ dashboard, records, columns }) {
2094
2173
  onDragStop: onLayoutChange,
2095
2174
  onResizeStop: onLayoutChange,
2096
2175
  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))
2176
+ children: panels.map((panel) => panelFrame(panel, canEditLayout))
2124
2177
  }
2125
2178
  )
2126
2179
  ] });