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

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.js CHANGED
@@ -7,7 +7,8 @@ import { ResponsiveScatterPlot } from '@nivo/scatterplot';
7
7
  import { ResponsiveHeatMap } from '@nivo/heatmap';
8
8
  import PivotTableUI from 'react-pivottable/PivotTableUI';
9
9
  import 'react-pivottable/pivottable.css';
10
- import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Code, Sparkles, X, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, Filter, Database, Send, GripVertical } from 'lucide-react';
10
+ import { TerminalSlider, TerminalInput, TerminalToggle, TerminalSelect } from '@quantumwake/terminal-ux-components';
11
+ import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Code, Sparkles, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, X, Filter, Database, Send, GripVertical } from 'lucide-react';
11
12
  import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
12
13
  import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
13
14
  import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
@@ -227,9 +228,15 @@ var DEFAULT_CHART_STYLE = {
227
228
  legendBold: false,
228
229
  legendHighlight: "",
229
230
  // Tick overflow handling.
230
- xTickRotation: -35,
231
+ xTickRotation: 0,
231
232
  yTickRotation: 0,
232
233
  tickTruncate: 0,
234
+ tickWrap: true,
235
+ tickWrapWidth: 14,
236
+ // Table cells.
237
+ cellWrap: true,
238
+ cellPrecision: 2,
239
+ cellTruncate: 0,
233
240
  // Series legend placement (charts that have one: pie, grouped bar).
234
241
  legendAnchor: "right",
235
242
  // Panel title (rendered by DashboardRenderer's panel header).
@@ -354,6 +361,78 @@ function MetricView({ records, config, value: presetValue }) {
354
361
  function InsightView({ config }) {
355
362
  return /* @__PURE__ */ jsx("div", { className: "h-full p-4 overflow-auto", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-midnight-text-body leading-relaxed whitespace-pre-wrap", children: config.text }) });
356
363
  }
364
+ function formatVal(value, s, numeric) {
365
+ let str = numeric ? Number(value).toLocaleString() : String(value);
366
+ if (s.tickTruncate > 0 && str.length > s.tickTruncate) str = `${str.slice(0, s.tickTruncate)}\u2026`;
367
+ return str;
368
+ }
369
+ function wrapText(str, width) {
370
+ if (width <= 0 || str.length <= width) return [str];
371
+ const words = str.split(/\s+/);
372
+ const lines = [];
373
+ let cur = "";
374
+ const push = (t) => {
375
+ if (t) lines.push(t);
376
+ };
377
+ for (const w of words) {
378
+ if (w.length > width) {
379
+ push(cur);
380
+ cur = "";
381
+ for (let i = 0; i < w.length; i += width) lines.push(w.slice(i, i + width));
382
+ continue;
383
+ }
384
+ if (!cur) cur = w;
385
+ else if (cur.length + 1 + w.length <= width) cur += ` ${w}`;
386
+ else {
387
+ push(cur);
388
+ cur = w;
389
+ }
390
+ }
391
+ push(cur);
392
+ if (lines.length > 4) {
393
+ const kept = lines.slice(0, 4);
394
+ kept[3] = `${kept[3].slice(0, Math.max(0, width - 1))}\u2026`;
395
+ return kept;
396
+ }
397
+ return lines;
398
+ }
399
+ function makeAxis(style, axis, columnName, opts2 = {}) {
400
+ const s = withStyleDefaults(style);
401
+ const isX = axis === "x";
402
+ const numeric = !!opts2.numeric;
403
+ const show = isX ? s.showXLegend : s.showYLegend;
404
+ const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
405
+ const rotate = isX ? s.xTickRotation : s.yTickRotation;
406
+ const out = { tickSize: 5, tickPadding: 5 };
407
+ if (show && label) {
408
+ out.legend = label;
409
+ out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
410
+ out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
411
+ }
412
+ if (s.tickWrap) {
413
+ out.renderTick = (tick) => {
414
+ const lines = wrapText(formatVal(tick.value, s, numeric), s.tickWrapWidth);
415
+ const firstDy = isX ? "0" : `${-((lines.length - 1) * 0.55)}em`;
416
+ return /* @__PURE__ */ jsxs("g", { transform: `translate(${tick.x},${tick.y})`, children: [
417
+ /* @__PURE__ */ jsx("line", { x2: isX ? 0 : -5, y2: isX ? 5 : 0, style: { stroke: s.textColor, strokeWidth: 1, opacity: 0.3 } }),
418
+ /* @__PURE__ */ jsx(
419
+ "text",
420
+ {
421
+ transform: `translate(${tick.textX},${tick.textY}) rotate(${rotate})`,
422
+ textAnchor: tick.textAnchor,
423
+ dominantBaseline: tick.textBaseline,
424
+ style: { fill: s.textColor, fontSize: s.fontSize },
425
+ children: lines.map((ln, i) => /* @__PURE__ */ jsx("tspan", { x: 0, dy: i === 0 ? firstDy : "1.1em", children: ln }, i))
426
+ }
427
+ )
428
+ ] });
429
+ };
430
+ } else {
431
+ out.tickRotation = rotate;
432
+ out.format = (v) => formatVal(v, s, numeric);
433
+ }
434
+ return out;
435
+ }
357
436
  function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, style }) {
358
437
  const computed = useMemo(() => {
359
438
  if (presetData || !records) return [];
@@ -361,7 +440,7 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
361
440
  return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
362
441
  }, [records, groupColumn, valueColumn, aggFn, presetData]);
363
442
  const data = presetData || computed;
364
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
443
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
365
444
  ResponsiveBar,
366
445
  {
367
446
  data,
@@ -371,8 +450,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
371
450
  padding: 0.3,
372
451
  colors: ["rgba(74, 222, 128, 0.8)"],
373
452
  borderColor: { from: "color", modifiers: [["darker", 1.6]] },
374
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
375
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
453
+ axisBottom: makeAxis(style, "x", groupColumn),
454
+ axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
376
455
  labelSkipWidth: 12,
377
456
  labelSkipHeight: 12,
378
457
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -393,7 +472,7 @@ function PieView({ records, groupColumn, data: presetData, style }) {
393
472
  }, [records, groupColumn, presetData]);
394
473
  const data = presetData || computed;
395
474
  const legend = legendConfig(style);
396
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
475
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
397
476
  ResponsivePie,
398
477
  {
399
478
  data,
@@ -429,7 +508,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
429
508
  }];
430
509
  }, [records, xColumn, yColumn, presetData]);
431
510
  const data = presetData || computed;
432
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
511
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
433
512
  ResponsiveLine,
434
513
  {
435
514
  data,
@@ -445,8 +524,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
445
524
  pointBorderWidth: 2,
446
525
  pointBorderColor: { from: "serieColor" },
447
526
  enableGridX: false,
448
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
449
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
527
+ axisBottom: makeAxis(style, "x", xColumn),
528
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
450
529
  useMesh: true,
451
530
  theme: buildNivoTheme(style)
452
531
  }
@@ -459,7 +538,7 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
459
538
  return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
460
539
  }, [records, xColumn, yColumn, presetData]);
461
540
  const data = presetData || computed;
462
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
541
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
463
542
  ResponsiveScatterPlot,
464
543
  {
465
544
  data,
@@ -468,8 +547,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
468
547
  yScale: { type: "linear", min: "auto", max: "auto" },
469
548
  colors: ["rgba(167, 139, 250, 0.7)"],
470
549
  nodeSize: 6,
471
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
472
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
550
+ axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
551
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
473
552
  useMesh: true,
474
553
  theme: buildNivoTheme(style)
475
554
  }
@@ -563,7 +642,7 @@ function HeatmapView({
563
642
  return /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
564
643
  }
565
644
  const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
566
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
645
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
567
646
  ResponsiveHeatMap,
568
647
  {
569
648
  data,
@@ -614,73 +693,25 @@ function PivotView({ records }) {
614
693
  ] });
615
694
  }
616
695
  function Section({ title, children }) {
617
- const { theme } = useDashboard();
618
- return /* @__PURE__ */ jsxs("div", { className: `py-3 border-t ${theme.border} first:border-t-0 first:pt-0`, children: [
619
- /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-midnight-text-muted/70 font-mono mb-2", children: title }),
620
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children })
696
+ return /* @__PURE__ */ jsxs("div", { className: "border-t border-midnight-border font-mono", style: { paddingTop: 12, paddingBottom: 12 }, children: [
697
+ /* @__PURE__ */ jsx("div", { className: "uppercase tracking-wider text-midnight-text-subdued", style: { fontSize: 10, marginBottom: 8 }, children: title }),
698
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", rowGap: 6 }, children })
621
699
  ] });
622
700
  }
623
701
  function Row({ label, children }) {
624
- return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[88px_1fr] items-center gap-2 min-h-[30px]", children: [
702
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "84px minmax(0, 1fr)", alignItems: "center", columnGap: 8, height: 32 }, children: [
625
703
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
626
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children })
704
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children })
627
705
  ] });
628
706
  }
629
707
  function ColorControl({ value, onChange }) {
630
- const { theme } = useDashboard();
631
708
  return /* @__PURE__ */ jsx(
632
709
  "input",
633
710
  {
634
711
  type: "color",
635
712
  value: /^#/.test(value) ? value : "#94a3b8",
636
713
  onChange: (e) => onChange(e.target.value),
637
- className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer p-0`
638
- }
639
- );
640
- }
641
- function SliderControl({ value, onChange, min, max, step = 1, unit = "" }) {
642
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
643
- /* @__PURE__ */ jsx(
644
- "input",
645
- {
646
- type: "range",
647
- min,
648
- max,
649
- step,
650
- value,
651
- onChange: (e) => onChange(Number(e.target.value)),
652
- className: "flex-1 h-1 accent-midnight-accent cursor-pointer"
653
- }
654
- ),
655
- /* @__PURE__ */ jsxs("span", { className: "w-10 shrink-0 text-right text-[11px] font-mono tabular-nums text-midnight-text-body", children: [
656
- value,
657
- unit
658
- ] })
659
- ] });
660
- }
661
- function TextControl({ value, onChange, placeholder }) {
662
- const { theme } = useDashboard();
663
- return /* @__PURE__ */ jsx(
664
- "input",
665
- {
666
- type: "text",
667
- value,
668
- placeholder,
669
- onChange: (e) => onChange(e.target.value),
670
- 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`
671
- }
672
- );
673
- }
674
- function Switch({ value, onChange }) {
675
- return /* @__PURE__ */ jsx(
676
- "button",
677
- {
678
- type: "button",
679
- role: "switch",
680
- "aria-checked": value,
681
- onClick: () => onChange(!value),
682
- className: `relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors ${value ? "bg-midnight-accent" : "bg-midnight-border"}`,
683
- children: /* @__PURE__ */ 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"}` })
714
+ className: "w-9 h-6 bg-transparent border border-midnight-border cursor-pointer p-0"
684
715
  }
685
716
  );
686
717
  }
@@ -688,21 +719,9 @@ function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
688
719
  const on = !!value;
689
720
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
690
721
  on && /* @__PURE__ */ jsx(ColorControl, { value, onChange }),
691
- /* @__PURE__ */ jsx(Switch, { value: on, onChange: (v) => onChange(v ? fallback : "") })
722
+ /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: on, onChange: (v) => onChange(v ? fallback : "") })
692
723
  ] });
693
724
  }
694
- function Choice({ value, options, onChange }) {
695
- const { theme } = useDashboard();
696
- return /* @__PURE__ */ jsx(
697
- "select",
698
- {
699
- value,
700
- onChange: (e) => onChange(e.target.value),
701
- 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`,
702
- children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
703
- }
704
- );
705
- }
706
725
  function ChartStyleControls({ style, onChange }) {
707
726
  const s = withStyleDefaults(style);
708
727
  const set = (key, value) => onChange({ ...s, [key]: value });
@@ -711,35 +730,42 @@ function ChartStyleControls({ style, onChange }) {
711
730
  /* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
712
731
  /* @__PURE__ */ jsx(Row, { label: "Text", children: /* @__PURE__ */ jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
713
732
  /* @__PURE__ */ jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
714
- /* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(SliderControl, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
733
+ /* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
715
734
  ] }),
716
735
  /* @__PURE__ */ jsxs(Section, { title: "Axis titles", children: [
717
- /* @__PURE__ */ jsx(Row, { label: "X title", children: /* @__PURE__ */ jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
718
- /* @__PURE__ */ jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsx(Switch, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
719
- /* @__PURE__ */ jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
720
- /* @__PURE__ */ jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsx(Switch, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
736
+ /* @__PURE__ */ jsx(Row, { label: "X title", children: /* @__PURE__ */ jsx(TerminalInput, { size: "small", value: s.xAxisLabel, placeholder: "(column)", onChange: (e) => set("xAxisLabel", e.target.value) }) }),
737
+ /* @__PURE__ */ jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
738
+ /* @__PURE__ */ jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsx(TerminalInput, { size: "small", value: s.yAxisLabel, placeholder: "(column)", onChange: (e) => set("yAxisLabel", e.target.value) }) }),
739
+ /* @__PURE__ */ jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
721
740
  /* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
722
- /* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
741
+ /* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
723
742
  /* @__PURE__ */ jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
724
743
  ] }),
725
744
  /* @__PURE__ */ jsxs(Section, { title: "Axis placement", children: [
726
- /* @__PURE__ */ jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
727
- /* @__PURE__ */ jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
728
- /* @__PURE__ */ jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
729
- /* @__PURE__ */ jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
745
+ /* @__PURE__ */ jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
746
+ /* @__PURE__ */ jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
747
+ /* @__PURE__ */ jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
748
+ /* @__PURE__ */ jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
730
749
  ] }),
731
750
  /* @__PURE__ */ jsxs(Section, { title: "Ticks", children: [
732
- /* @__PURE__ */ jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
733
- /* @__PURE__ */ jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
734
- /* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(SliderControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
751
+ /* @__PURE__ */ jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.tickWrap, onChange: (v) => set("tickWrap", v) }) }),
752
+ /* @__PURE__ */ jsx(Row, { label: "Wrap width", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.tickWrapWidth, min: 4, max: 40, onChange: (v) => set("tickWrapWidth", v) }) }),
753
+ /* @__PURE__ */ jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
754
+ /* @__PURE__ */ jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
755
+ /* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
756
+ ] }),
757
+ /* @__PURE__ */ jsxs(Section, { title: "Table cells", children: [
758
+ /* @__PURE__ */ jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.cellWrap, onChange: (v) => set("cellWrap", v) }) }),
759
+ /* @__PURE__ */ jsx(Row, { label: "Decimals", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.cellPrecision, min: 0, max: 8, onChange: (v) => set("cellPrecision", v) }) }),
760
+ /* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.cellTruncate, min: 0, max: 80, onChange: (v) => set("cellTruncate", v) }) })
735
761
  ] }),
736
762
  /* @__PURE__ */ jsxs(Section, { title: "Panel title", children: [
737
- /* @__PURE__ */ jsx(Row, { label: "Align", children: /* @__PURE__ */ jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
738
- /* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
763
+ /* @__PURE__ */ jsx(Row, { label: "Align", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
764
+ /* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
739
765
  /* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
740
766
  /* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
741
767
  ] }),
742
- /* @__PURE__ */ jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsx(Row, { label: "Position", children: /* @__PURE__ */ jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
768
+ /* @__PURE__ */ jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsx(Row, { label: "Position", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
743
769
  ] });
744
770
  }
745
771
  var AGGREGATES = [
@@ -1347,7 +1373,7 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1347
1373
  const keys = presetKeys || yFields.map((yf) => `${yf.agg}(${yf.name})`);
1348
1374
  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)"];
1349
1375
  const legend = legendConfig(style);
1350
- return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsx(
1376
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
1351
1377
  ResponsiveBar,
1352
1378
  {
1353
1379
  data,
@@ -1357,8 +1383,8 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1357
1383
  margin: { top: 20, right: 120, bottom: 60, left: 60 },
1358
1384
  padding: 0.3,
1359
1385
  colors: colors.slice(0, keys.length),
1360
- axisBottom: { tickSize: 5, tickPadding: 5, tickRotation: -35 },
1361
- axisLeft: { tickSize: 5, tickPadding: 5, format: (v) => Number(v).toLocaleString() },
1386
+ axisBottom: makeAxis(style, "x", xFields[0]?.name || ""),
1387
+ axisLeft: makeAxis(style, "y", "", { numeric: true }),
1362
1388
  labelSkipWidth: 12,
1363
1389
  labelSkipHeight: 12,
1364
1390
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -1464,6 +1490,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1464
1490
  const [style, setStyle] = useState(DEFAULT_CHART_STYLE);
1465
1491
  const [showStyle, setShowStyle] = useState(false);
1466
1492
  const [showFields, setShowFields] = useState(false);
1493
+ const [fillContainer, setFillContainer] = useState(false);
1467
1494
  const [sqlRows, setSqlRows] = useState(null);
1468
1495
  const [sqlLoading, setSqlLoading] = useState(false);
1469
1496
  const [sqlError, setSqlError] = useState(null);
@@ -1578,12 +1605,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1578
1605
  }
1579
1606
  }
1580
1607
  config.style = style;
1608
+ config.fill = fillContainer;
1581
1609
  onSave({
1582
1610
  title: title || `${chartType} chart`,
1583
1611
  type: chartType === "grouped-bar" ? "bar" : chartType,
1584
1612
  config,
1585
- width: 6,
1586
- height: 2
1613
+ width: fillContainer ? 12 : 6,
1614
+ height: fillContainer ? 8 : 2
1587
1615
  });
1588
1616
  };
1589
1617
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full", children: [
@@ -1739,6 +1767,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1739
1767
  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"
1740
1768
  }
1741
1769
  ),
1770
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1771
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", title: "Span the full dashboard width and height", children: "Maximize size" }),
1772
+ /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: fillContainer, onChange: setFillContainer })
1773
+ ] }),
1742
1774
  /* @__PURE__ */ jsx(
1743
1775
  "button",
1744
1776
  {
@@ -1835,14 +1867,26 @@ function panelSql(type, config) {
1835
1867
  if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
1836
1868
  return "";
1837
1869
  }
1838
- function DataTable({ rows, columns }) {
1870
+ function formatCell(value, s) {
1871
+ if (value == null) return "";
1872
+ if (typeof value === "object") return JSON.stringify(value);
1873
+ let str;
1874
+ const n = typeof value === "number" ? value : Number(value);
1875
+ if (value !== "" && typeof value !== "boolean" && Number.isFinite(n)) {
1876
+ str = Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { minimumFractionDigits: s.cellPrecision, maximumFractionDigits: s.cellPrecision });
1877
+ } else {
1878
+ str = String(value);
1879
+ }
1880
+ if (s.cellTruncate > 0 && str.length > s.cellTruncate) str = `${str.slice(0, s.cellTruncate)}\u2026`;
1881
+ return str;
1882
+ }
1883
+ function DataTable({ rows, columns, style }) {
1884
+ const s = withStyleDefaults(style);
1839
1885
  const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
1886
+ 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]";
1840
1887
  return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1841
1888
  /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-midnight-elevated", children: /* @__PURE__ */ jsx("tr", { children: colNames.map((n) => /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-left text-midnight-text-muted font-mono border-b border-midnight-border", children: n }, n)) }) }),
1842
- /* @__PURE__ */ jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
1843
- const cellValue = r[n];
1844
- return /* @__PURE__ */ 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);
1845
- }) }, i)) })
1889
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => /* @__PURE__ */ jsx("td", { className: cellCls, children: formatCell(r[n], s) }, n)) }, i)) })
1846
1890
  ] }) });
1847
1891
  }
1848
1892
  function SqlPanel({ panel }) {
@@ -1937,7 +1981,7 @@ function SqlPanel({ panel }) {
1937
1981
  const v = first ? Object.values(first)[0] : 0;
1938
1982
  chart = /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1939
1983
  } else if (chartType === "table") {
1940
- chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns });
1984
+ chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns, style: config.style });
1941
1985
  } else {
1942
1986
  const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1943
1987
  switch (chartType) {
@@ -2001,7 +2045,7 @@ function PanelContent({ panel, records, columns }) {
2001
2045
  case "insight":
2002
2046
  return /* @__PURE__ */ jsx(InsightView, { config: { text: config.text } });
2003
2047
  case "table":
2004
- return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
2048
+ return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name), style: config.style });
2005
2049
  default:
2006
2050
  return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
2007
2051
  "Unknown panel type: ",
@@ -2009,6 +2053,7 @@ function PanelContent({ panel, records, columns }) {
2009
2053
  ] });
2010
2054
  }
2011
2055
  }
2056
+ var PanelChart = PanelContent;
2012
2057
  var GRID_COLS = 12;
2013
2058
  var ROW_HEIGHT = 80;
2014
2059
  var GRID_MARGIN = 10;
@@ -2040,10 +2085,11 @@ var GridLayoutWithWidth = WidthProvider(GridLayout);
2040
2085
  function buildLayout(panels) {
2041
2086
  let cx = 0, cy = 0, rowH = 0;
2042
2087
  return panels.map((p) => {
2043
- const w = Math.min(Math.max(p.width || 6, 1), GRID_COLS);
2044
- const h = Math.max(p.height || 4, 1);
2088
+ const fill = !!p.config?.fill;
2089
+ const w = fill ? GRID_COLS : Math.min(Math.max(p.width || 6, 1), GRID_COLS);
2090
+ const h = fill ? Math.max(p.height || 8, 6) : Math.max(p.height || 4, 1);
2045
2091
  if (typeof p.x === "number" && typeof p.y === "number") {
2046
- return { i: p.id, x: p.x, y: p.y, w, h, minW: 2, minH: 2 };
2092
+ return { i: p.id, x: fill ? 0 : p.x, y: p.y, w, h, minW: 2, minH: 2 };
2047
2093
  }
2048
2094
  if (cx + w > GRID_COLS) {
2049
2095
  cx = 0;
@@ -2057,7 +2103,7 @@ function buildLayout(panels) {
2057
2103
  });
2058
2104
  }
2059
2105
  function DashboardRenderer({ dashboard, records, columns }) {
2060
- const { theme, removePanel, persistLayout } = useDashboard();
2106
+ const { theme, removePanel, persistLayout, panelHeaderExtra } = useDashboard();
2061
2107
  const { canEditPanels, canEditLayout } = useCapabilities();
2062
2108
  useInjectRglCss();
2063
2109
  const panels = dashboard?.panels ?? [];
@@ -2067,11 +2113,48 @@ function DashboardRenderer({ dashboard, records, columns }) {
2067
2113
  persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
2068
2114
  }, [persistLayout]);
2069
2115
  if (!dashboard) return null;
2070
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2071
- dashboard.insights && /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2072
- /* @__PURE__ */ jsx(Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2073
- /* @__PURE__ */ jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2116
+ const panelFrame = (panel, draggable) => /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden h-full`, children: [
2117
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${draggable ? DRAG_HANDLE : ""}`, children: [
2118
+ /* @__PURE__ */ jsx(
2119
+ "span",
2120
+ {
2121
+ className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2122
+ style: {
2123
+ textAlign: panel.config?.style?.titleAlign || "left",
2124
+ fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2125
+ background: panel.config?.style?.titleBackground || void 0,
2126
+ color: panel.config?.style?.titleColor || void 0
2127
+ },
2128
+ children: panel.title || panel.type
2129
+ }
2130
+ ),
2131
+ panelHeaderExtra && // Host-injected per-panel affordance (e.g. the viewer's
2132
+ // copy-embed-link icon). Stop drag from hijacking the click.
2133
+ /* @__PURE__ */ jsx("span", { onMouseDown: (e) => e.stopPropagation(), className: "flex items-center", children: panelHeaderExtra(panel) }),
2134
+ canEditPanels && removePanel && /* @__PURE__ */ jsx(
2135
+ "button",
2136
+ {
2137
+ onMouseDown: (e) => e.stopPropagation(),
2138
+ onClick: () => removePanel(panel.id),
2139
+ className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2140
+ children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
2141
+ }
2142
+ )
2074
2143
  ] }),
2144
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ViewLoading, {}), children: /* @__PURE__ */ jsx(PanelContent, { panel, records, columns }) }) })
2145
+ ] }, panel.id);
2146
+ const insightsBanner = dashboard.insights ? /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2147
+ /* @__PURE__ */ jsx(Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2148
+ /* @__PURE__ */ jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2149
+ ] }) : null;
2150
+ if (panels.length === 1) {
2151
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2 h-full min-h-[60vh]", children: [
2152
+ insightsBanner,
2153
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: panelFrame(panels[0], false) })
2154
+ ] });
2155
+ }
2156
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2157
+ insightsBanner,
2075
2158
  /* @__PURE__ */ jsx(
2076
2159
  GridLayoutWithWidth,
2077
2160
  {
@@ -2086,33 +2169,7 @@ function DashboardRenderer({ dashboard, records, columns }) {
2086
2169
  onDragStop: onLayoutChange,
2087
2170
  onResizeStop: onLayoutChange,
2088
2171
  compactType: canEditLayout ? "vertical" : null,
2089
- children: panels.map((panel) => /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden`, children: [
2090
- /* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${canEditLayout ? DRAG_HANDLE : ""}`, children: [
2091
- /* @__PURE__ */ jsx(
2092
- "span",
2093
- {
2094
- className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2095
- style: {
2096
- textAlign: panel.config?.style?.titleAlign || "left",
2097
- fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2098
- background: panel.config?.style?.titleBackground || void 0,
2099
- color: panel.config?.style?.titleColor || void 0
2100
- },
2101
- children: panel.title || panel.type
2102
- }
2103
- ),
2104
- canEditPanels && removePanel && /* @__PURE__ */ jsx(
2105
- "button",
2106
- {
2107
- onMouseDown: (e) => e.stopPropagation(),
2108
- onClick: () => removePanel(panel.id),
2109
- className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2110
- children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
2111
- }
2112
- )
2113
- ] }),
2114
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ViewLoading, {}), children: /* @__PURE__ */ jsx(PanelContent, { panel, records, columns }) }) })
2115
- ] }, panel.id))
2172
+ children: panels.map((panel) => panelFrame(panel, canEditLayout))
2116
2173
  }
2117
2174
  )
2118
2175
  ] });
@@ -2440,6 +2497,6 @@ function DataExplorer({
2440
2497
  ] });
2441
2498
  }
2442
2499
 
2443
- export { BarView, ChartBuilder, ChartStyleControls, DEFAULT_CHART_STYLE, DashboardProvider, DashboardRenderer, DataExplorer, HeatmapView, InsightView, LEGEND_ANCHORS, LineView, MetricView, PieView, PivotView, ScatterView, SqlConsole, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
2500
+ export { BarView, ChartBuilder, ChartStyleControls, DEFAULT_CHART_STYLE, DashboardProvider, DashboardRenderer, DataExplorer, HeatmapView, InsightView, LEGEND_ANCHORS, LineView, MetricView, PanelChart, PieView, PivotView, ScatterView, SqlConsole, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
2444
2501
  //# sourceMappingURL=index.js.map
2445
2502
  //# sourceMappingURL=index.js.map