@speakai/ui
Advanced tools
@@ -159,4 +159,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| measure?: string; | ||
| /** Chart rendering: grouped bars (default) or a donut. */ | ||
| chartType?: "bar" | "donut"; | ||
| /** Rendering: grouped bars (default), a donut, or a comparison table. */ | ||
| chartType?: "bar" | "donut" | "table"; | ||
| } | ||
@@ -163,0 +163,0 @@ interface FieldDistributionWidgetProps { |
@@ -159,4 +159,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| measure?: string; | ||
| /** Chart rendering: grouped bars (default) or a donut. */ | ||
| chartType?: "bar" | "donut"; | ||
| /** Rendering: grouped bars (default), a donut, or a comparison table. */ | ||
| chartType?: "bar" | "donut" | "table"; | ||
| } | ||
@@ -163,0 +163,0 @@ interface FieldDistributionWidgetProps { |
@@ -416,2 +416,37 @@ "use client"; | ||
| } | ||
| function FieldDistributionTable({ | ||
| rows, | ||
| prev, | ||
| valueFormatter, | ||
| measureLabel | ||
| }) { | ||
| const total = rows.reduce((sum, d) => sum + d.nTimes, 0); | ||
| const prevByText = new Map(prev.map((d) => [d.text, d.nTimes])); | ||
| const hasPrev = prev.length > 0; | ||
| return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-sm", children: [ | ||
| /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b border-border text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [ | ||
| /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-2 pr-3 text-left", children: "Category" }), | ||
| /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-2 px-3 text-right", children: measureLabel ?? "Count" }), | ||
| /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-2 px-3 text-right", children: "% of total" }), | ||
| hasPrev && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-2 pl-3 text-right", children: "vs prev period" }) | ||
| ] }) }), | ||
| /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((d) => { | ||
| const pct = total > 0 ? Math.round(d.nTimes / total * 100) : 0; | ||
| const prevN = prevByText.get(d.text); | ||
| const delta = prevN != null ? d.nTimes - prevN : null; | ||
| const deltaPct = prevN != null && prevN > 0 ? Math.round((d.nTimes - prevN) / prevN * 100) : null; | ||
| const arrow = delta == null || delta === 0 ? "" : delta > 0 ? "\u25B2" : "\u25BC"; | ||
| const sign = (n) => n > 0 ? "+" : "\u2212"; | ||
| return /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b border-border/50 last:border-0", children: [ | ||
| /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pr-3 text-left text-foreground", children: d.text }), | ||
| /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 px-3 text-right font-medium tabular-nums text-foreground", children: valueFormatter(d.nTimes) }), | ||
| /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "py-2 px-3 text-right tabular-nums text-muted-foreground", children: [ | ||
| pct, | ||
| "%" | ||
| ] }), | ||
| hasPrev && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pl-3 text-right tabular-nums text-muted-foreground", children: delta == null ? "\u2014" : delta === 0 ? "no change" : `${arrow} ${sign(delta)}${Math.abs(delta)}${deltaPct != null ? ` (${sign(deltaPct)}${Math.abs(deltaPct)}%)` : ""}` }) | ||
| ] }, d.text); | ||
| }) }) | ||
| ] }) }); | ||
| } | ||
| function FieldDistributionWidget({ | ||
@@ -446,2 +481,14 @@ data, | ||
| const isPercent = measure === "percent"; | ||
| if (config?.chartType === "table") { | ||
| const countFormatter = measure === "duration" ? formatDurationHuman : (v) => formatCount(v); | ||
| return /* @__PURE__ */ jsxRuntime.jsx( | ||
| FieldDistributionTable, | ||
| { | ||
| rows: data?.insights ?? [], | ||
| prev: data?.compareInsights ?? [], | ||
| valueFormatter: countFormatter, | ||
| measureLabel: labels.measureLabel | ||
| } | ||
| ); | ||
| } | ||
| let compareInsights = data?.compareInsights ?? []; | ||
@@ -448,0 +495,0 @@ if (isPercent) { |
@@ -415,2 +415,37 @@ "use client"; | ||
| } | ||
| function FieldDistributionTable({ | ||
| rows, | ||
| prev, | ||
| valueFormatter, | ||
| measureLabel | ||
| }) { | ||
| const total = rows.reduce((sum, d) => sum + d.nTimes, 0); | ||
| const prevByText = new Map(prev.map((d) => [d.text, d.nTimes])); | ||
| const hasPrev = prev.length > 0; | ||
| return /* @__PURE__ */ jsx("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [ | ||
| /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [ | ||
| /* @__PURE__ */ jsx("th", { className: "py-2 pr-3 text-left", children: "Category" }), | ||
| /* @__PURE__ */ jsx("th", { className: "py-2 px-3 text-right", children: measureLabel ?? "Count" }), | ||
| /* @__PURE__ */ jsx("th", { className: "py-2 px-3 text-right", children: "% of total" }), | ||
| hasPrev && /* @__PURE__ */ jsx("th", { className: "py-2 pl-3 text-right", children: "vs prev period" }) | ||
| ] }) }), | ||
| /* @__PURE__ */ jsx("tbody", { children: rows.map((d) => { | ||
| const pct = total > 0 ? Math.round(d.nTimes / total * 100) : 0; | ||
| const prevN = prevByText.get(d.text); | ||
| const delta = prevN != null ? d.nTimes - prevN : null; | ||
| const deltaPct = prevN != null && prevN > 0 ? Math.round((d.nTimes - prevN) / prevN * 100) : null; | ||
| const arrow = delta == null || delta === 0 ? "" : delta > 0 ? "\u25B2" : "\u25BC"; | ||
| const sign = (n) => n > 0 ? "+" : "\u2212"; | ||
| return /* @__PURE__ */ jsxs("tr", { className: "border-b border-border/50 last:border-0", children: [ | ||
| /* @__PURE__ */ jsx("td", { className: "py-2 pr-3 text-left text-foreground", children: d.text }), | ||
| /* @__PURE__ */ jsx("td", { className: "py-2 px-3 text-right font-medium tabular-nums text-foreground", children: valueFormatter(d.nTimes) }), | ||
| /* @__PURE__ */ jsxs("td", { className: "py-2 px-3 text-right tabular-nums text-muted-foreground", children: [ | ||
| pct, | ||
| "%" | ||
| ] }), | ||
| hasPrev && /* @__PURE__ */ jsx("td", { className: "py-2 pl-3 text-right tabular-nums text-muted-foreground", children: delta == null ? "\u2014" : delta === 0 ? "no change" : `${arrow} ${sign(delta)}${Math.abs(delta)}${deltaPct != null ? ` (${sign(deltaPct)}${Math.abs(deltaPct)}%)` : ""}` }) | ||
| ] }, d.text); | ||
| }) }) | ||
| ] }) }); | ||
| } | ||
| function FieldDistributionWidget({ | ||
@@ -445,2 +480,14 @@ data, | ||
| const isPercent = measure === "percent"; | ||
| if (config?.chartType === "table") { | ||
| const countFormatter = measure === "duration" ? formatDurationHuman : (v) => formatCount(v); | ||
| return /* @__PURE__ */ jsx( | ||
| FieldDistributionTable, | ||
| { | ||
| rows: data?.insights ?? [], | ||
| prev: data?.compareInsights ?? [], | ||
| valueFormatter: countFormatter, | ||
| measureLabel: labels.measureLabel | ||
| } | ||
| ); | ||
| } | ||
| let compareInsights = data?.compareInsights ?? []; | ||
@@ -447,0 +494,0 @@ if (isPercent) { |
+1
-1
| { | ||
| "name": "@speakai/ui", | ||
| "version": "0.24.0", | ||
| "version": "0.25.0", | ||
| "description": "Speak AI design system — React + Tailwind UI components with dark/light mode, WCAG AA, and full CSS variable customization", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
2815805
0.65%24796
0.38%