@elabs-ai/components-data 4.2.0 → 5.0.0

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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-3OVN7MMO.js +2 -0
  3. package/dist/chunk-3OVN7MMO.js.map +1 -0
  4. package/dist/chunk-3UFWYVDS.js +2 -0
  5. package/dist/chunk-3UFWYVDS.js.map +1 -0
  6. package/dist/chunk-47ZBQZYS.js +47 -0
  7. package/dist/chunk-47ZBQZYS.js.map +1 -0
  8. package/dist/chunk-5K2IFOV2.js +2 -0
  9. package/dist/chunk-5K2IFOV2.js.map +1 -0
  10. package/dist/chunk-677ORWI6.js +17 -0
  11. package/dist/chunk-677ORWI6.js.map +1 -0
  12. package/dist/chunk-7ZYUY3UL.js +2 -0
  13. package/dist/chunk-7ZYUY3UL.js.map +1 -0
  14. package/dist/chunk-BEYHV3ZX.js +37 -0
  15. package/dist/chunk-BEYHV3ZX.js.map +1 -0
  16. package/dist/chunk-DBPJ7LVM.js +57 -0
  17. package/dist/chunk-DBPJ7LVM.js.map +1 -0
  18. package/dist/chunk-E3V3SYY7.js +87 -0
  19. package/dist/chunk-E3V3SYY7.js.map +1 -0
  20. package/dist/chunk-GBO56756.js +81 -0
  21. package/dist/chunk-GBO56756.js.map +1 -0
  22. package/dist/chunk-HG2YMSRM.js +95 -0
  23. package/dist/chunk-HG2YMSRM.js.map +1 -0
  24. package/dist/chunk-K2GHSRCP.js +1823 -0
  25. package/dist/chunk-K2GHSRCP.js.map +1 -0
  26. package/dist/chunk-KCB33PD3.js +43 -0
  27. package/dist/chunk-KCB33PD3.js.map +1 -0
  28. package/dist/chunk-MV2YVXEZ.js +67 -0
  29. package/dist/chunk-MV2YVXEZ.js.map +1 -0
  30. package/dist/chunk-NKVJUQYW.js +82 -0
  31. package/dist/chunk-NKVJUQYW.js.map +1 -0
  32. package/dist/chunk-OASM53LL.js +29 -0
  33. package/dist/chunk-OASM53LL.js.map +1 -0
  34. package/dist/chunk-RZ5MYZOA.js +2 -0
  35. package/dist/chunk-RZ5MYZOA.js.map +1 -0
  36. package/dist/chunk-SK6347WB.js +100 -0
  37. package/dist/chunk-SK6347WB.js.map +1 -0
  38. package/dist/chunk-TBB33PHQ.js +58 -0
  39. package/dist/chunk-TBB33PHQ.js.map +1 -0
  40. package/dist/chunk-VCLA45IC.js +150 -0
  41. package/dist/chunk-VCLA45IC.js.map +1 -0
  42. package/dist/chunk-XN6O6Z2N.js +58 -0
  43. package/dist/chunk-XN6O6Z2N.js.map +1 -0
  44. package/dist/chunk-ZFEW36RG.js +77 -0
  45. package/dist/chunk-ZFEW36RG.js.map +1 -0
  46. package/dist/chunk-ZQXJ34VJ.js +34 -0
  47. package/dist/chunk-ZQXJ34VJ.js.map +1 -0
  48. package/dist/chunk-ZUCGV2XD.js +42 -0
  49. package/dist/chunk-ZUCGV2XD.js.map +1 -0
  50. package/dist/column-picker/column-picker.js +8 -0
  51. package/dist/column-picker/column-picker.js.map +1 -0
  52. package/dist/column-picker/index.js +9 -0
  53. package/dist/column-picker/index.js.map +1 -0
  54. package/dist/data-table/card-layout.js +11 -0
  55. package/dist/data-table/card-layout.js.map +1 -0
  56. package/dist/data-table/cell-scales.js +26 -0
  57. package/dist/data-table/cell-scales.js.map +1 -0
  58. package/dist/data-table/cells/bar-cell.js +14 -0
  59. package/dist/data-table/cells/bar-cell.js.map +1 -0
  60. package/dist/data-table/cells/category-legend.js +9 -0
  61. package/dist/data-table/cells/category-legend.js.map +1 -0
  62. package/dist/data-table/cells/columns-cell.js +13 -0
  63. package/dist/data-table/cells/columns-cell.js.map +1 -0
  64. package/dist/data-table/cells/heatmap-cell.js +13 -0
  65. package/dist/data-table/cells/heatmap-cell.js.map +1 -0
  66. package/dist/data-table/cells/markdown-cell.js +13 -0
  67. package/dist/data-table/cells/markdown-cell.js.map +1 -0
  68. package/dist/data-table/cells/sparkline-cell.js +13 -0
  69. package/dist/data-table/cells/sparkline-cell.js.map +1 -0
  70. package/dist/data-table/column-meta.js +12 -0
  71. package/dist/data-table/column-meta.js.map +1 -0
  72. package/dist/data-table/data-table.js +23 -0
  73. package/dist/data-table/data-table.js.map +1 -0
  74. package/dist/data-table/index.js +23 -0
  75. package/dist/data-table/index.js.map +1 -0
  76. package/dist/data-table/ranks-column.js +13 -0
  77. package/dist/data-table/ranks-column.js.map +1 -0
  78. package/dist/data-table/sticky-rows.js +10 -0
  79. package/dist/data-table/sticky-rows.js.map +1 -0
  80. package/dist/data-table/use-table-breakpoint.js +13 -0
  81. package/dist/data-table/use-table-breakpoint.js.map +1 -0
  82. package/dist/facet-filter/facet-filter.js +8 -0
  83. package/dist/facet-filter/facet-filter.js.map +1 -0
  84. package/dist/facet-filter/index.js +9 -0
  85. package/dist/facet-filter/index.js.map +1 -0
  86. package/dist/filter-bar/filter-bar.js +8 -0
  87. package/dist/filter-bar/filter-bar.js.map +1 -0
  88. package/dist/filter-bar/filter-chip.js +9 -0
  89. package/dist/filter-bar/filter-chip.js.map +1 -0
  90. package/dist/filter-bar/index.js +13 -0
  91. package/dist/filter-bar/index.js.map +1 -0
  92. package/dist/index.d.ts +547 -8
  93. package/dist/index.js +102 -1512
  94. package/dist/index.js.map +1 -1
  95. package/dist/search-input/index.js +9 -0
  96. package/dist/search-input/index.js.map +1 -0
  97. package/dist/search-input/search-input.js +9 -0
  98. package/dist/search-input/search-input.js.map +1 -0
  99. package/dist/to-csv.js +10 -0
  100. package/dist/to-csv.js.map +1 -0
  101. package/package.json +7 -7
  102. package/src/data-table/card-layout.test.tsx +26 -0
  103. package/src/data-table/card-layout.tsx +74 -0
  104. package/src/data-table/cell-scales.test.ts +118 -0
  105. package/src/data-table/cell-scales.ts +246 -0
  106. package/src/data-table/cells/bar-cell.test.tsx +158 -0
  107. package/src/data-table/cells/bar-cell.tsx +135 -0
  108. package/src/data-table/cells/category-legend.test.tsx +37 -0
  109. package/src/data-table/cells/category-legend.tsx +58 -0
  110. package/src/data-table/cells/columns-cell.test.tsx +27 -0
  111. package/src/data-table/cells/columns-cell.tsx +79 -0
  112. package/src/data-table/cells/heatmap-cell.test.tsx +31 -0
  113. package/src/data-table/cells/heatmap-cell.tsx +116 -0
  114. package/src/data-table/cells/markdown-cell.test.tsx +36 -0
  115. package/src/data-table/cells/markdown-cell.tsx +91 -0
  116. package/src/data-table/cells/sparkline-cell.test.tsx +43 -0
  117. package/src/data-table/cells/sparkline-cell.tsx +113 -0
  118. package/src/data-table/column-meta.test.ts +42 -0
  119. package/src/data-table/column-meta.ts +282 -0
  120. package/src/data-table/data-table.stories.tsx +733 -1
  121. package/src/data-table/data-table.test.tsx +486 -0
  122. package/src/data-table/data-table.tsx +965 -165
  123. package/src/data-table/ranks-column.test.tsx +65 -0
  124. package/src/data-table/ranks-column.tsx +91 -0
  125. package/src/data-table/sticky-rows.test.ts +16 -0
  126. package/src/data-table/sticky-rows.ts +77 -0
  127. package/src/data-table/use-table-breakpoint.test.ts +14 -0
  128. package/src/data-table/use-table-breakpoint.ts +67 -0
  129. package/src/filter-bar/filter-chip.stories.tsx +1 -0
  130. package/src/index.ts +64 -0
  131. package/src/templates-data-app.stories.tsx +7 -2
package/README.md CHANGED
@@ -38,7 +38,7 @@ import { ThemeProvider } from "@elabs-ai/components-tokens";
38
38
 
39
39
  ## What's in it
40
40
 
41
- 6 exported components — including `ColumnPicker`, `DataTable`, `FacetFilter`, `FilterBar`, `FilterChip`.
41
+ 18 exported components — including `BarCell`, `CategoryLegend`, `ColumnPicker`, `ColumnsCell`, `DATA_TABLE_BREAKPOINT_THRESHOLDS`.
42
42
 
43
43
  Don't guess the API — ask the CLI:
44
44
 
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-3OVN7MMO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-3UFWYVDS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ // src/data-table/sticky-rows.ts
4
+ var EMPTY = { top: [], bottom: [] };
5
+ function stickyRowPinning(data, stickyRows, getRowId) {
6
+ if (!stickyRows) return EMPTY;
7
+ const top = [];
8
+ const bottom = [];
9
+ data.forEach((row, index) => {
10
+ const position = stickyRows(row, index);
11
+ if (!position) return;
12
+ const id = getRowId ? getRowId(row, index) : String(index);
13
+ (position === "top" ? top : bottom).push(id);
14
+ });
15
+ return top.length === 0 && bottom.length === 0 ? EMPTY : { top, bottom };
16
+ }
17
+ function withoutStickyRows(factory) {
18
+ return (table) => {
19
+ const compute = factory(table);
20
+ let lastModel;
21
+ let lastPinning;
22
+ let lastResult;
23
+ return () => {
24
+ const model = compute();
25
+ const pinning = table.getState().rowPinning;
26
+ if (model === lastModel && pinning === lastPinning && lastResult) return lastResult;
27
+ lastModel = model;
28
+ lastPinning = pinning;
29
+ const sticky = /* @__PURE__ */ new Set([...pinning?.top ?? [], ...pinning?.bottom ?? []]);
30
+ if (sticky.size === 0) {
31
+ lastResult = model;
32
+ return model;
33
+ }
34
+ const rows = model.rows.filter((row) => !sticky.has(row.id));
35
+ const flatRows = model.flatRows.filter((row) => !sticky.has(row.id));
36
+ const rowsById = Object.fromEntries(flatRows.map((row) => [row.id, row]));
37
+ lastResult = { rows, flatRows, rowsById };
38
+ return lastResult;
39
+ };
40
+ };
41
+ }
42
+
43
+ export {
44
+ stickyRowPinning,
45
+ withoutStickyRows
46
+ };
47
+ //# sourceMappingURL=chunk-47ZBQZYS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data-table/sticky-rows.ts"],"sourcesContent":["import type { RowModel, RowPinningState, Table } from \"@tanstack/react-table\";\n\n/**\n * sticky-rows.ts — `DataTable`'s `stickyRows` (RM-123): rows such as an\n * \"average\" or \"total\" that stay at the top or bottom of EVERY page and after\n * every sort.\n *\n * Built on TanStack's own row pinning (`keepPinnedRows`), so the table model is\n * unchanged: a sticky row keeps its id, its selection and its `data` index; it\n * is only rendered outside the sorted / paged centre rows.\n */\n\nexport type DataTableStickyRowPosition = \"top\" | \"bottom\";\n\n/** Decides, per record, whether its row sticks to the top or the bottom (or neither). */\nexport type DataTableStickyRows<TData> = (\n row: TData,\n index: number,\n) => DataTableStickyRowPosition | undefined;\n\nconst EMPTY: RowPinningState = { top: [], bottom: [] };\n\n/**\n * The TanStack `rowPinning` state for `stickyRows` over `data`, in data order.\n * Row ids follow TanStack's own rule: `getRowId(row, index)`, else the index.\n */\nexport function stickyRowPinning<TData>(\n data: readonly TData[],\n stickyRows: DataTableStickyRows<TData> | undefined,\n getRowId?: (row: TData, index: number) => string,\n): RowPinningState {\n if (!stickyRows) return EMPTY;\n const top: string[] = [];\n const bottom: string[] = [];\n data.forEach((row, index) => {\n const position = stickyRows(row, index);\n if (!position) return;\n const id = getRowId ? getRowId(row, index) : String(index);\n (position === \"top\" ? top : bottom).push(id);\n });\n return top.length === 0 && bottom.length === 0 ? EMPTY : { top, bottom };\n}\n\n/**\n * Wraps a filtered-row-model factory so sticky (pinned) rows leave the flow the\n * table sorts and pages: every page holds `pageSize` ordinary rows and the page\n * count ignores the sticky ones, which `keepPinnedRows` still renders at the\n * top / bottom of every page. Sticky rows are also exempt from search and\n * filters — an \"average\" row describes the whole table, not the matches.\n */\nexport function withoutStickyRows<TData>(\n factory: (table: Table<TData>) => () => RowModel<TData>,\n): (table: Table<TData>) => () => RowModel<TData> {\n return (table) => {\n const compute = factory(table);\n let lastModel: RowModel<TData> | undefined;\n let lastPinning: RowPinningState | undefined;\n let lastResult: RowModel<TData> | undefined;\n return () => {\n const model = compute();\n const pinning = table.getState().rowPinning;\n if (model === lastModel && pinning === lastPinning && lastResult) return lastResult;\n lastModel = model;\n lastPinning = pinning;\n const sticky = new Set([...(pinning?.top ?? []), ...(pinning?.bottom ?? [])]);\n if (sticky.size === 0) {\n lastResult = model;\n return model;\n }\n const rows = model.rows.filter((row) => !sticky.has(row.id));\n const flatRows = model.flatRows.filter((row) => !sticky.has(row.id));\n const rowsById = Object.fromEntries(flatRows.map((row) => [row.id, row]));\n lastResult = { rows, flatRows, rowsById };\n return lastResult;\n };\n };\n}\n"],"mappings":";;;AAoBA,IAAM,QAAyB,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAC,EAAE;AAM9C,SAAS,iBACd,MACA,YACA,UACiB;AACjB,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,MAAgB,CAAC;AACvB,QAAM,SAAmB,CAAC;AAC1B,OAAK,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,WAAW,WAAW,KAAK,KAAK;AACtC,QAAI,CAAC,SAAU;AACf,UAAM,KAAK,WAAW,SAAS,KAAK,KAAK,IAAI,OAAO,KAAK;AACzD,KAAC,aAAa,QAAQ,MAAM,QAAQ,KAAK,EAAE;AAAA,EAC7C,CAAC;AACD,SAAO,IAAI,WAAW,KAAK,OAAO,WAAW,IAAI,QAAQ,EAAE,KAAK,OAAO;AACzE;AASO,SAAS,kBACd,SACgD;AAChD,SAAO,CAAC,UAAU;AAChB,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,WAAO,MAAM;AACX,YAAM,QAAQ,QAAQ;AACtB,YAAM,UAAU,MAAM,SAAS,EAAE;AACjC,UAAI,UAAU,aAAa,YAAY,eAAe,WAAY,QAAO;AACzE,kBAAY;AACZ,oBAAc;AACd,YAAM,SAAS,oBAAI,IAAI,CAAC,GAAI,SAAS,OAAO,CAAC,GAAI,GAAI,SAAS,UAAU,CAAC,CAAE,CAAC;AAC5E,UAAI,OAAO,SAAS,GAAG;AACrB,qBAAa;AACb,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;AAC3D,YAAM,WAAW,MAAM,SAAS,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;AACnE,YAAM,WAAW,OAAO,YAAY,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AACxE,mBAAa,EAAE,MAAM,UAAU,SAAS;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-5K2IFOV2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ // src/filter-bar/filter-bar.tsx
4
+ import "react";
5
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function FilterBar({ children, actions, className }) {
8
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center justify-between gap-2", className), children: [
9
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children }),
10
+ actions ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: actions }) : null
11
+ ] });
12
+ }
13
+
14
+ export {
15
+ FilterBar
16
+ };
17
+ //# sourceMappingURL=chunk-677ORWI6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/filter-bar/filter-bar.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FilterBarProps {\n /** Left cluster: search + facet filters. */\n children: ReactNode;\n /** Right cluster: column picker, export, primary actions. */\n actions?: ReactNode;\n className?: string;\n}\n\n/** Horizontal toolbar that groups table filters and actions. */\nexport function FilterBar({ children, actions, className }: FilterBarProps) {\n return (\n <div className={cn(\"flex flex-wrap items-center justify-between gap-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-2\">{children}</div>\n {actions ? <div className=\"flex items-center gap-2\">{actions}</div> : null}\n </div>\n );\n}\n"],"mappings":";;;AAAA,OAA+B;AAC/B,SAAS,UAAU;AAaf,SACE,KADF;AAFG,SAAS,UAAU,EAAE,UAAU,SAAS,UAAU,GAAmB;AAC1E,SACE,qBAAC,SAAI,WAAW,GAAG,qDAAqD,SAAS,GAC/E;AAAA,wBAAC,SAAI,WAAU,qCAAqC,UAAS;AAAA,IAC5D,UAAU,oBAAC,SAAI,WAAU,2BAA2B,mBAAQ,IAAS;AAAA,KACxE;AAEJ;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-7ZYUY3UL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ // src/data-table/use-table-breakpoint.ts
4
+ import { useCallback, useLayoutEffect, useState } from "react";
5
+ var DATA_TABLE_BREAKPOINT_THRESHOLDS = { narrow: 450 };
6
+ function tableBreakpointForWidth(width) {
7
+ if (!Number.isFinite(width) || width <= 0) return "wide";
8
+ return width < DATA_TABLE_BREAKPOINT_THRESHOLDS.narrow ? "narrow" : "wide";
9
+ }
10
+ function useTableBreakpoint(enabled = true) {
11
+ const [node, setNode] = useState(null);
12
+ const [breakpoint, setBreakpoint] = useState("wide");
13
+ const ref = useCallback((next) => setNode(next), []);
14
+ useLayoutEffect(() => {
15
+ if (!enabled || !node) return void 0;
16
+ const update = (width) => {
17
+ const next = tableBreakpointForWidth(width);
18
+ setBreakpoint((prev) => prev === next ? prev : next);
19
+ };
20
+ update(node.getBoundingClientRect().width);
21
+ if (typeof ResizeObserver === "undefined") return void 0;
22
+ const observer = new ResizeObserver((entries) => {
23
+ const entry = entries[entries.length - 1];
24
+ if (entry) update(entry.contentRect.width);
25
+ });
26
+ observer.observe(node);
27
+ return () => observer.disconnect();
28
+ }, [enabled, node]);
29
+ return { ref, breakpoint: enabled ? breakpoint : "wide" };
30
+ }
31
+
32
+ export {
33
+ DATA_TABLE_BREAKPOINT_THRESHOLDS,
34
+ tableBreakpointForWidth,
35
+ useTableBreakpoint
36
+ };
37
+ //# sourceMappingURL=chunk-BEYHV3ZX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data-table/use-table-breakpoint.ts"],"sourcesContent":["\"use client\";\n\n/**\n * use-table-breakpoint.ts — `DataTable`'s own container-measured breakpoint.\n *\n * A DELIBERATE COPY of `charts`' `useMeasuredChartBreakpoint`\n * (`packages/charts/src/charts/chart-breakpoint.ts`, ADR 0039): `data` may not\n * import `charts` (one-way dependency graph), and a table's threshold is its\n * own. Keep the two in step when either changes; `.claude/rules/data.md`\n * carries the same table.\n *\n * | Helper | Package | Tiers | Thresholds (container px) |\n * | --------------------------- | ------- | ------------------------- | ------------------------- |\n * | `useMeasuredChartBreakpoint` | charts | narrow / medium / wide | narrow < 480, medium < 768 |\n * | `useTableBreakpoint` | data | narrow / wide | narrow < 450 |\n *\n * Measured on the table's OWN width, never the viewport. A width of `0` (not\n * measured yet: server render, first pass, `display: none`) is `wide`, so a\n * first paint matches the pre-breakpoint table. A boundary belongs to the\n * wider tier (450 → wide).\n */\n\nimport { useCallback, useLayoutEffect, useState } from \"react\";\n\n/** A table container's width tier. */\nexport type DataTableBreakpoint = \"narrow\" | \"wide\";\n\n/** A width (CSS px) below `narrow` is narrow. */\nexport const DATA_TABLE_BREAKPOINT_THRESHOLDS = { narrow: 450 } as const;\n\n/** The tier for a measured container width. */\nexport function tableBreakpointForWidth(width: number): DataTableBreakpoint {\n if (!Number.isFinite(width) || width <= 0) return \"wide\";\n return width < DATA_TABLE_BREAKPOINT_THRESHOLDS.narrow ? \"narrow\" : \"wide\";\n}\n\n/**\n * Measures the element the returned `ref` is attached to with a\n * `ResizeObserver` and returns its tier. Re-renders only when the TIER changes.\n * `enabled: false` skips the observer entirely (a table that uses no\n * breakpoint behaviour pays nothing).\n */\nexport function useTableBreakpoint<E extends Element = HTMLDivElement>(\n enabled = true,\n): { ref: (node: E | null) => void; breakpoint: DataTableBreakpoint } {\n const [node, setNode] = useState<E | null>(null);\n const [breakpoint, setBreakpoint] = useState<DataTableBreakpoint>(\"wide\");\n const ref = useCallback((next: E | null) => setNode(next), []);\n\n useLayoutEffect(() => {\n if (!enabled || !node) return undefined;\n const update = (width: number) => {\n const next = tableBreakpointForWidth(width);\n setBreakpoint((prev) => (prev === next ? prev : next));\n };\n update(node.getBoundingClientRect().width);\n if (typeof ResizeObserver === \"undefined\") return undefined;\n const observer = new ResizeObserver((entries) => {\n const entry = entries[entries.length - 1];\n if (entry) update(entry.contentRect.width);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, [enabled, node]);\n\n return { ref, breakpoint: enabled ? breakpoint : \"wide\" };\n}\n"],"mappings":";;;AAsBA,SAAS,aAAa,iBAAiB,gBAAgB;AAMhD,IAAM,mCAAmC,EAAE,QAAQ,IAAI;AAGvD,SAAS,wBAAwB,OAAoC;AAC1E,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO;AAClD,SAAO,QAAQ,iCAAiC,SAAS,WAAW;AACtE;AAQO,SAAS,mBACd,UAAU,MAC0D;AACpE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,IAAI;AAC/C,QAAM,CAAC,YAAY,aAAa,IAAI,SAA8B,MAAM;AACxE,QAAM,MAAM,YAAY,CAAC,SAAmB,QAAQ,IAAI,GAAG,CAAC,CAAC;AAE7D,kBAAgB,MAAM;AACpB,QAAI,CAAC,WAAW,CAAC,KAAM,QAAO;AAC9B,UAAM,SAAS,CAAC,UAAkB;AAChC,YAAM,OAAO,wBAAwB,KAAK;AAC1C,oBAAc,CAAC,SAAU,SAAS,OAAO,OAAO,IAAK;AAAA,IACvD;AACA,WAAO,KAAK,sBAAsB,EAAE,KAAK;AACzC,QAAI,OAAO,mBAAmB,YAAa,QAAO;AAClD,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,YAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AACxC,UAAI,MAAO,QAAO,MAAM,YAAY,KAAK;AAAA,IAC3C,CAAC;AACD,aAAS,QAAQ,IAAI;AACrB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,SAAS,IAAI,CAAC;AAElB,SAAO,EAAE,KAAK,YAAY,UAAU,aAAa,OAAO;AAC1D;","names":[]}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ // src/column-picker/column-picker.tsx
4
+ import "@tanstack/react-table";
5
+ import { forwardRef } from "react";
6
+ import {
7
+ Button,
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuTrigger,
14
+ useLocale
15
+ } from "@elabs-ai/components-ui";
16
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ function ColumnPickerInner({ table, label, className, ...props }, ref) {
19
+ const { t } = useLocale();
20
+ const resolvedLabel = label ?? t("data.columnPicker.label");
21
+ const columns = table.getAllColumns().filter((c) => c.getCanHide());
22
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
23
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { ref, variant: "outline", size: "sm", className: cn(className), ...props, children: resolvedLabel }) }),
24
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "min-w-[12rem]", children: [
25
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("data.columnPicker.toggleColumns") }),
26
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
27
+ columns.map((column) => /* @__PURE__ */ jsxs(
28
+ DropdownMenuItem,
29
+ {
30
+ onSelect: (e) => {
31
+ e.preventDefault();
32
+ column.toggleVisibility(!column.getIsVisible());
33
+ },
34
+ children: [
35
+ /* @__PURE__ */ jsx(
36
+ "span",
37
+ {
38
+ "aria-hidden": "true",
39
+ className: "flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard " + (column.getIsVisible() ? "border-primary bg-primary text-primary-foreground" : "border-input"),
40
+ children: column.getIsVisible() ? "\u2713" : ""
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsx("span", { className: "capitalize", children: column.id })
44
+ ]
45
+ },
46
+ column.id
47
+ ))
48
+ ] })
49
+ ] });
50
+ }
51
+ ColumnPickerInner.displayName = "ColumnPicker";
52
+ var ColumnPicker = forwardRef(ColumnPickerInner);
53
+
54
+ export {
55
+ ColumnPicker
56
+ };
57
+ //# sourceMappingURL=chunk-DBPJ7LVM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/column-picker/column-picker.tsx"],"sourcesContent":["import { type Table } from \"@tanstack/react-table\";\nimport type { ButtonHTMLAttributes, ReactElement, Ref } from \"react\";\nimport { forwardRef } from \"react\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface ColumnPickerProps<TData> extends ButtonHTMLAttributes<HTMLButtonElement> {\n table: Table<TData>;\n /** Trigger label. Defaults to \"Columns\". */\n label?: string;\n}\n\nfunction ColumnPickerInner<TData>(\n { table, label, className, ...props }: ColumnPickerProps<TData>,\n ref: Ref<HTMLButtonElement>,\n) {\n const { t } = useLocale();\n const resolvedLabel = label ?? t(\"data.columnPicker.label\");\n const columns = table.getAllColumns().filter((c) => c.getCanHide());\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button ref={ref} variant=\"outline\" size=\"sm\" className={cn(className)} {...props}>\n {resolvedLabel}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-[12rem]\">\n <DropdownMenuLabel>{t(\"data.columnPicker.toggleColumns\")}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {columns.map((column) => (\n <DropdownMenuItem\n key={column.id}\n onSelect={(e) => {\n e.preventDefault();\n column.toggleVisibility(!column.getIsVisible());\n }}\n >\n <span\n aria-hidden=\"true\"\n className={\n \"flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard \" +\n (column.getIsVisible()\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input\")\n }\n >\n {column.getIsVisible() ? \"✓\" : \"\"}\n </span>\n <span className=\"capitalize\">{column.id}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nColumnPickerInner.displayName = \"ColumnPicker\";\n\n// React.forwardRef strips the generic parameter — the cast below restores it\n// (same pattern as DataTable's public export) so callers get full type\n// inference on `table` while still being able to forward a ref to the\n// trigger `Button`.\n//\n// `disabled` (available via the extended `ButtonHTMLAttributes`, forwarded to\n// the trigger) is how a consumer signals a pending fetch (D5 — the app owns\n// fetch state; see loading-states.md).\nexport const ColumnPicker = forwardRef(ColumnPickerInner) as <TData>(\n props: ColumnPickerProps<TData> & { ref?: Ref<HTMLButtonElement> },\n) => ReactElement | null;\n"],"mappings":";;;AAAA,OAA2B;AAE3B,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU;AAkBX,cAQE,YARF;AAVR,SAAS,kBACP,EAAE,OAAO,OAAO,WAAW,GAAG,MAAM,GACpC,KACA;AACA,QAAM,EAAE,EAAE,IAAI,UAAU;AACxB,QAAM,gBAAgB,SAAS,EAAE,yBAAyB;AAC1D,QAAM,UAAU,MAAM,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC;AAClE,SACE,qBAAC,gBACC;AAAA,wBAAC,uBAAoB,SAAO,MAC1B,8BAAC,UAAO,KAAU,SAAQ,WAAU,MAAK,MAAK,WAAW,GAAG,SAAS,GAAI,GAAG,OACzE,yBACH,GACF;AAAA,IACA,qBAAC,uBAAoB,OAAM,OAAM,WAAU,iBACzC;AAAA,0BAAC,qBAAmB,YAAE,iCAAiC,GAAE;AAAA,MACzD,oBAAC,yBAAsB;AAAA,MACtB,QAAQ,IAAI,CAAC,WACZ;AAAA,QAAC;AAAA;AAAA,UAEC,UAAU,CAAC,MAAM;AACf,cAAE,eAAe;AACjB,mBAAO,iBAAiB,CAAC,OAAO,aAAa,CAAC;AAAA,UAChD;AAAA,UAEA;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,eAAY;AAAA,gBACZ,WACE,2GACC,OAAO,aAAa,IACjB,sDACA;AAAA,gBAGL,iBAAO,aAAa,IAAI,WAAM;AAAA;AAAA,YACjC;AAAA,YACA,oBAAC,UAAK,WAAU,cAAc,iBAAO,IAAG;AAAA;AAAA;AAAA,QAjBnC,OAAO;AAAA,MAkBd,CACD;AAAA,OACH;AAAA,KACF;AAEJ;AAEA,kBAAkB,cAAc;AAUzB,IAAM,eAAe,WAAW,iBAAiB;","names":[]}
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ // src/search-input/search-input.tsx
4
+ import { forwardRef, useId, useRef } from "react";
5
+ import { Input, useLocale } from "@elabs-ai/components-ui";
6
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
7
+ import { SearchIcon } from "@elabs-ai/components-icons";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var SearchInput = forwardRef(function SearchInput2({
10
+ value,
11
+ onValueChange,
12
+ label,
13
+ placeholder,
14
+ className,
15
+ containerClassName,
16
+ disabled,
17
+ id: idProp,
18
+ ...props
19
+ }, forwardedRef) {
20
+ const { t } = useLocale();
21
+ const resolvedLabel = label ?? t("data.searchInput.label");
22
+ const resolvedPlaceholder = placeholder ?? t("data.searchInput.placeholder");
23
+ const generatedId = useId();
24
+ const id = idProp ?? generatedId;
25
+ const inputRef = useRef(null);
26
+ const setRefs = (node) => {
27
+ inputRef.current = node;
28
+ if (typeof forwardedRef === "function") forwardedRef(node);
29
+ else if (forwardedRef) forwardedRef.current = node;
30
+ };
31
+ const handleClear = () => {
32
+ onValueChange("");
33
+ inputRef.current?.focus();
34
+ };
35
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative w-full max-w-xs", containerClassName), children: [
36
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "sr-only", children: resolvedLabel }),
37
+ /* @__PURE__ */ jsx(
38
+ SearchIcon,
39
+ {
40
+ size: 16,
41
+ className: "pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsx(
45
+ Input,
46
+ {
47
+ ref: setRefs,
48
+ id,
49
+ value,
50
+ onChange: (e) => onValueChange(e.target.value),
51
+ placeholder: resolvedPlaceholder,
52
+ disabled,
53
+ className: cn("ps-8", value && "pe-8", className),
54
+ ...props
55
+ }
56
+ ),
57
+ value && !disabled ? /* @__PURE__ */ jsx(
58
+ "button",
59
+ {
60
+ type: "button",
61
+ onClick: handleClear,
62
+ "aria-label": t("data.searchInput.clear"),
63
+ className: "absolute end-2 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground transition-colors duration-fast ease-standard hover:text-foreground focus-ring animate-in fade-in zoom-in-95 duration-fast ease-entrance",
64
+ children: /* @__PURE__ */ jsx(
65
+ "svg",
66
+ {
67
+ width: "14",
68
+ height: "14",
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ stroke: "currentColor",
72
+ strokeWidth: "2",
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ "aria-hidden": "true",
76
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
77
+ }
78
+ )
79
+ }
80
+ ) : null
81
+ ] });
82
+ });
83
+
84
+ export {
85
+ SearchInput
86
+ };
87
+ //# sourceMappingURL=chunk-E3V3SYY7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/search-input/search-input.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useId, useRef, type InputHTMLAttributes } from \"react\";\nimport { Input, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { SearchIcon } from \"@elabs-ai/components-icons\";\n\nexport interface SearchInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\"\n> {\n value: string;\n onValueChange: (value: string) => void;\n /** Visually-hidden accessible label. Defaults to the localized \"Search\" microcopy. */\n label?: string;\n containerClassName?: string;\n}\n\n/**\n * Search field with a leading icon and a clear button. Controlled.\n *\n * `disabled` (available via the extended `InputHTMLAttributes`) is how a\n * consumer signals a pending fetch (D5 — the app owns fetch state, this\n * control just reflects it; see loading-states.md). It is forwarded to the\n * `<Input>` explicitly AND gates the clear button — while disabled the clear\n * affordance is hidden so it can't mutate the filter mid-request (#269/#8).\n */\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(function SearchInput(\n {\n value,\n onValueChange,\n label,\n placeholder,\n className,\n containerClassName,\n disabled,\n id: idProp,\n ...props\n },\n forwardedRef,\n) {\n const { t } = useLocale();\n const resolvedLabel = label ?? t(\"data.searchInput.label\");\n const resolvedPlaceholder = placeholder ?? t(\"data.searchInput.placeholder\");\n const generatedId = useId();\n const id = idProp ?? generatedId;\n const inputRef = useRef<HTMLInputElement>(null);\n\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n };\n\n const handleClear = () => {\n onValueChange(\"\");\n // The clear button unmounts the instant `value` becomes falsy — without\n // this, the browser drops focus to <body> instead of leaving it\n // somewhere the keyboard user can keep typing.\n inputRef.current?.focus();\n };\n\n return (\n <div className={cn(\"relative w-full max-w-xs\", containerClassName)}>\n <label htmlFor={id} className=\"sr-only\">\n {resolvedLabel}\n </label>\n <SearchIcon\n size={16}\n className=\"pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <Input\n ref={setRefs}\n id={id}\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n placeholder={resolvedPlaceholder}\n disabled={disabled}\n className={cn(\"ps-8\", value && \"pe-8\", className)}\n {...props}\n />\n {value && !disabled ? (\n <button\n type=\"button\"\n onClick={handleClear}\n aria-label={t(\"data.searchInput.clear\")}\n className=\"absolute end-2 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground transition-colors duration-fast ease-standard hover:text-foreground focus-ring animate-in fade-in zoom-in-95 duration-fast ease-entrance\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n ) : null}\n </div>\n );\n});\n"],"mappings":";;;AAEA,SAAS,YAAY,OAAO,cAAwC;AACpE,SAAS,OAAO,iBAAiB;AACjC,SAAS,UAAU;AACnB,SAAS,kBAAkB;AA0DvB,SACE,KADF;AApCG,IAAM,cAAc,WAA+C,SAASA,aACjF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AAAA,EACJ,GAAG;AACL,GACA,cACA;AACA,QAAM,EAAE,EAAE,IAAI,UAAU;AACxB,QAAM,gBAAgB,SAAS,EAAE,wBAAwB;AACzD,QAAM,sBAAsB,eAAe,EAAE,8BAA8B;AAC3E,QAAM,cAAc,MAAM;AAC1B,QAAM,KAAK,UAAU;AACrB,QAAM,WAAW,OAAyB,IAAI;AAE9C,QAAM,UAAU,CAAC,SAAkC;AACjD,aAAS,UAAU;AACnB,QAAI,OAAO,iBAAiB,WAAY,cAAa,IAAI;AAAA,aAChD,aAAc,cAAa,UAAU;AAAA,EAChD;AAEA,QAAM,cAAc,MAAM;AACxB,kBAAc,EAAE;AAIhB,aAAS,SAAS,MAAM;AAAA,EAC1B;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,4BAA4B,kBAAkB,GAC/D;AAAA,wBAAC,WAAM,SAAS,IAAI,WAAU,WAC3B,yBACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,WAAU;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,QAC7C,aAAa;AAAA,QACb;AAAA,QACA,WAAW,GAAG,QAAQ,SAAS,QAAQ,SAAS;AAAA,QAC/C,GAAG;AAAA;AAAA,IACN;AAAA,IACC,SAAS,CAAC,WACT;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,cAAY,EAAE,wBAAwB;AAAA,QACtC,WAAU;AAAA,QAEV;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,QAAO;AAAA,YACP,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,eAAc;AAAA,YACd,gBAAe;AAAA,YACf,eAAY;AAAA,YAEZ,8BAAC,UAAK,GAAE,wBAAuB;AAAA;AAAA,QACjC;AAAA;AAAA,IACF,IACE;AAAA,KACN;AAEJ,CAAC;","names":["SearchInput"]}
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import {
3
+ BAR_CELL_NEGATIVE_COLOR,
4
+ BAR_CELL_POSITIVE_COLOR
5
+ } from "./chunk-SK6347WB.js";
6
+
7
+ // src/data-table/cells/columns-cell.tsx
8
+ import { forwardRef } from "react";
9
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var COLUMNS_CELL_HAIRLINE_WIDTH = 0.65;
12
+ var VIEW_WIDTH = 100;
13
+ var GAP = 12;
14
+ var ColumnsCell = forwardRef(function ColumnsCell2({ values, domain, label, height = 24, className, ...props }, ref) {
15
+ const h = Math.max(0, height);
16
+ const lo = Math.min(0, domain?.min ?? 0);
17
+ const hi = Math.max(0, domain?.max ?? 0);
18
+ const span = hi - lo;
19
+ const y = (v) => span > 0 ? (hi - v) / span * h : h;
20
+ const slot = values.length > 0 ? VIEW_WIDTH / values.length : 0;
21
+ const width = Math.max(0, slot * (1 - GAP / 100));
22
+ return /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ ref,
26
+ "data-slot": "columns-cell",
27
+ className: cn("flex min-w-0 items-center", className),
28
+ ...props,
29
+ children: [
30
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
31
+ /* @__PURE__ */ jsxs(
32
+ "svg",
33
+ {
34
+ "aria-hidden": "true",
35
+ "data-slot": "columns-cell-svg",
36
+ "data-y-min": lo,
37
+ "data-y-max": hi,
38
+ viewBox: `0 0 ${VIEW_WIDTH} ${h}`,
39
+ preserveAspectRatio: "none",
40
+ className: "block min-w-8 flex-1",
41
+ style: { height: h },
42
+ children: [
43
+ values.map(
44
+ ({ key, value: v }, i) => v === null ? null : /* @__PURE__ */ jsx(
45
+ "rect",
46
+ {
47
+ "data-slot": "columns-cell-column",
48
+ x: i * slot + (slot - width) / 2,
49
+ y: Math.min(y(v), y(0)),
50
+ width,
51
+ height: Math.max(0, Math.abs(y(v) - y(0))),
52
+ fill: v < 0 ? BAR_CELL_NEGATIVE_COLOR : BAR_CELL_POSITIVE_COLOR
53
+ },
54
+ key
55
+ )
56
+ ),
57
+ /* @__PURE__ */ jsx(
58
+ "line",
59
+ {
60
+ x1: 0,
61
+ x2: VIEW_WIDTH,
62
+ y1: y(0),
63
+ y2: y(0),
64
+ stroke: "var(--chart-grid)",
65
+ strokeWidth: COLUMNS_CELL_HAIRLINE_WIDTH,
66
+ vectorEffect: "non-scaling-stroke"
67
+ }
68
+ )
69
+ ]
70
+ }
71
+ )
72
+ ]
73
+ }
74
+ );
75
+ });
76
+
77
+ export {
78
+ COLUMNS_CELL_HAIRLINE_WIDTH,
79
+ ColumnsCell
80
+ };
81
+ //# sourceMappingURL=chunk-GBO56756.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data-table/cells/columns-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { NumericExtent } from \"../cell-scales\";\nimport { BAR_CELL_NEGATIVE_COLOR, BAR_CELL_POSITIVE_COLOR } from \"./bar-cell\";\n\n/** The zero rule's width (px) — the charts hairline, mirrored. */\nexport const COLUMNS_CELL_HAIRLINE_WIDTH = 0.65;\nconst VIEW_WIDTH = 100;\nconst GAP = 12; // % of a slot left empty between columns\n\nexport interface ColumnsCellProps extends HTMLAttributes<HTMLDivElement> {\n /** One entry per row key, in x order; a `null` value draws no column. */\n values: readonly { key: string; value: number | null }[];\n /** The value domain: this row's own extent or the column's. Zero is always added. */\n domain: NumericExtent | null;\n /** Every value as text, for screen readers, sorting and copy. */\n label: string;\n /** Drawing height in px. Default 24. */\n height?: number;\n}\n\n/** In-cell mini columns: a ≤ 40-line inline SVG, columns grow from a zero rule. */\nexport const ColumnsCell = forwardRef<HTMLDivElement, ColumnsCellProps>(function ColumnsCell(\n { values, domain, label, height = 24, className, ...props },\n ref,\n) {\n const h = Math.max(0, height);\n const lo = Math.min(0, domain?.min ?? 0);\n const hi = Math.max(0, domain?.max ?? 0);\n const span = hi - lo;\n const y = (v: number) => (span > 0 ? ((hi - v) / span) * h : h);\n const slot = values.length > 0 ? VIEW_WIDTH / values.length : 0;\n const width = Math.max(0, slot * (1 - GAP / 100));\n return (\n <div\n ref={ref}\n data-slot=\"columns-cell\"\n className={cn(\"flex min-w-0 items-center\", className)}\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n <svg\n aria-hidden=\"true\"\n data-slot=\"columns-cell-svg\"\n data-y-min={lo}\n data-y-max={hi}\n viewBox={`0 0 ${VIEW_WIDTH} ${h}`}\n preserveAspectRatio=\"none\"\n className=\"block min-w-8 flex-1\"\n style={{ height: h }}\n >\n {values.map(({ key, value: v }, i) =>\n v === null ? null : (\n <rect\n key={key}\n data-slot=\"columns-cell-column\"\n x={i * slot + (slot - width) / 2}\n y={Math.min(y(v), y(0))}\n width={width}\n height={Math.max(0, Math.abs(y(v) - y(0)))}\n fill={v < 0 ? BAR_CELL_NEGATIVE_COLOR : BAR_CELL_POSITIVE_COLOR}\n />\n ),\n )}\n <line\n x1={0}\n x2={VIEW_WIDTH}\n y1={y(0)}\n y2={y(0)}\n stroke=\"var(--chart-grid)\"\n strokeWidth={COLUMNS_CELL_HAIRLINE_WIDTH}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n </div>\n );\n});\n"],"mappings":";;;;;;;AAEA,SAAS,kBAAuC;AAChD,SAAS,UAAU;AAuCb,cACA,YADA;AAlCC,IAAM,8BAA8B;AAC3C,IAAM,aAAa;AACnB,IAAM,MAAM;AAcL,IAAM,cAAc,WAA6C,SAASA,aAC/E,EAAE,QAAQ,QAAQ,OAAO,SAAS,IAAI,WAAW,GAAG,MAAM,GAC1D,KACA;AACA,QAAM,IAAI,KAAK,IAAI,GAAG,MAAM;AAC5B,QAAM,KAAK,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;AACvC,QAAM,KAAK,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;AACvC,QAAM,OAAO,KAAK;AAClB,QAAM,IAAI,CAAC,MAAe,OAAO,KAAM,KAAK,KAAK,OAAQ,IAAI;AAC7D,QAAM,OAAO,OAAO,SAAS,IAAI,aAAa,OAAO,SAAS;AAC9D,QAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,IAAI,MAAM,IAAI;AAChD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW,GAAG,6BAA6B,SAAS;AAAA,MACnD,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,WAAW,iBAAM;AAAA,QACjC;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,aAAU;AAAA,YACV,cAAY;AAAA,YACZ,cAAY;AAAA,YACZ,SAAS,OAAO,UAAU,IAAI,CAAC;AAAA,YAC/B,qBAAoB;AAAA,YACpB,WAAU;AAAA,YACV,OAAO,EAAE,QAAQ,EAAE;AAAA,YAElB;AAAA,qBAAO;AAAA,gBAAI,CAAC,EAAE,KAAK,OAAO,EAAE,GAAG,MAC9B,MAAM,OAAO,OACX;AAAA,kBAAC;AAAA;AAAA,oBAEC,aAAU;AAAA,oBACV,GAAG,IAAI,QAAQ,OAAO,SAAS;AAAA,oBAC/B,GAAG,KAAK,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAAA,oBACtB;AAAA,oBACA,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAAA,oBACzC,MAAM,IAAI,IAAI,0BAA0B;AAAA;AAAA,kBANnC;AAAA,gBAOP;AAAA,cAEJ;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAI;AAAA,kBACJ,IAAI;AAAA,kBACJ,IAAI,EAAE,CAAC;AAAA,kBACP,IAAI,EAAE,CAAC;AAAA,kBACP,QAAO;AAAA,kBACP,aAAa;AAAA,kBACb,cAAa;AAAA;AAAA,cACf;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["ColumnsCell"]}
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import {
3
+ BAR_CELL_POSITIVE_COLOR
4
+ } from "./chunk-SK6347WB.js";
5
+
6
+ // src/data-table/cells/sparkline-cell.tsx
7
+ import { forwardRef } from "react";
8
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var SPARKLINE_CELL_STROKE_WIDTH = 1.5;
11
+ var VIEW_WIDTH = 100;
12
+ var PAD = 2;
13
+ function sparkPath(values, domain, height) {
14
+ const span = domain.max - domain.min;
15
+ const step = values.length > 1 ? VIEW_WIDTH / (values.length - 1) : 0;
16
+ const y = (v) => span > 0 ? PAD + (1 - (v - domain.min) / span) * Math.max(0, height - 2 * PAD) : height / 2;
17
+ let d = "";
18
+ let open = false;
19
+ values.forEach((v, i) => {
20
+ if (v === null) return void (open = false);
21
+ d += `${open ? "L" : "M"}${(i * step).toFixed(2)} ${y(v).toFixed(2)}`;
22
+ open = true;
23
+ });
24
+ return d;
25
+ }
26
+ var SparklineCell = forwardRef(function SparklineCell2({ values, domain, label, ends, endsWidth, fill = false, height = 24, className, ...props }, ref) {
27
+ const h = Math.max(0, height);
28
+ const d = domain ? sparkPath(values, domain, h) : "";
29
+ const area = fill && d ? `${d}L${VIEW_WIDTH} ${h}L0 ${h}Z` : "";
30
+ return /* @__PURE__ */ jsxs(
31
+ "div",
32
+ {
33
+ ref,
34
+ "data-slot": "sparkline-cell",
35
+ className: cn("flex min-w-0 items-center gap-1.5", className),
36
+ ...props,
37
+ children: [
38
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
39
+ ends && /* @__PURE__ */ jsx(
40
+ "span",
41
+ {
42
+ "aria-hidden": "true",
43
+ className: "shrink-0 whitespace-nowrap text-end text-meta text-muted-foreground tabular-nums",
44
+ style: endsWidth?.[0] ? { width: `${endsWidth[0]}ch` } : void 0,
45
+ children: ends[0]
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsxs(
49
+ "svg",
50
+ {
51
+ "aria-hidden": "true",
52
+ "data-slot": "sparkline-cell-svg",
53
+ "data-y-min": domain?.min,
54
+ "data-y-max": domain?.max,
55
+ viewBox: `0 0 ${VIEW_WIDTH} ${h}`,
56
+ preserveAspectRatio: "none",
57
+ className: "block min-w-8 flex-1 overflow-visible",
58
+ style: { height: h },
59
+ children: [
60
+ area && /* @__PURE__ */ jsx("path", { d: area, fill: BAR_CELL_POSITIVE_COLOR, fillOpacity: 0.2, stroke: "none" }),
61
+ /* @__PURE__ */ jsx(
62
+ "path",
63
+ {
64
+ "data-slot": "sparkline-cell-line",
65
+ d,
66
+ fill: "none",
67
+ stroke: BAR_CELL_POSITIVE_COLOR,
68
+ strokeWidth: SPARKLINE_CELL_STROKE_WIDTH,
69
+ strokeLinejoin: "round",
70
+ strokeLinecap: "round",
71
+ vectorEffect: "non-scaling-stroke"
72
+ }
73
+ )
74
+ ]
75
+ }
76
+ ),
77
+ ends && /* @__PURE__ */ jsx(
78
+ "span",
79
+ {
80
+ "aria-hidden": "true",
81
+ className: "shrink-0 whitespace-nowrap text-start text-meta text-foreground tabular-nums",
82
+ style: endsWidth?.[1] ? { width: `${endsWidth[1]}ch` } : void 0,
83
+ children: ends[1]
84
+ }
85
+ )
86
+ ]
87
+ }
88
+ );
89
+ });
90
+
91
+ export {
92
+ SPARKLINE_CELL_STROKE_WIDTH,
93
+ SparklineCell
94
+ };
95
+ //# sourceMappingURL=chunk-HG2YMSRM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data-table/cells/sparkline-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { NumericExtent } from \"../cell-scales\";\nimport { BAR_CELL_POSITIVE_COLOR } from \"./bar-cell\";\n\n/** The line's stroke width (px, unscaled by the viewBox). */\nexport const SPARKLINE_CELL_STROKE_WIDTH = 1.5;\nconst VIEW_WIDTH = 100;\nconst PAD = 2;\n\nexport interface SparklineCellProps extends HTMLAttributes<HTMLDivElement> {\n values: readonly (number | null)[];\n /** The y domain: this row's own extent (`range: \"cell\"`) or the column's. */\n domain: NumericExtent | null;\n /** Every value as text, for screen readers, sorting and copy. */\n label: string;\n /** Printed first / last values (with `labels: \"ends\"`). */\n ends?: readonly [first: string, last: string];\n /**\n * Room reserved for those two labels, in `ch` (`labelBoxCh` over the whole\n * column). The drawing takes what they leave over, so one reservation per\n * column keeps every row's line on the same x scale.\n */\n endsWidth?: readonly [first: number, last: number];\n fill?: boolean;\n /** Drawing height in px. Default 24. */\n height?: number;\n}\n\n/** `M`/`L` path through the finite points; a `null` breaks the line. */\nfunction sparkPath(values: readonly (number | null)[], domain: NumericExtent, height: number) {\n const span = domain.max - domain.min;\n const step = values.length > 1 ? VIEW_WIDTH / (values.length - 1) : 0;\n const y = (v: number) =>\n span > 0 ? PAD + (1 - (v - domain.min) / span) * Math.max(0, height - 2 * PAD) : height / 2;\n let d = \"\";\n let open = false;\n values.forEach((v, i) => {\n if (v === null) return void (open = false);\n d += `${open ? \"L\" : \"M\"}${(i * step).toFixed(2)} ${y(v).toFixed(2)}`;\n open = true;\n });\n return d;\n}\n\n/** An in-cell sparkline: a ≤ 40-line inline SVG on a shared or own y domain. */\nexport const SparklineCell = forwardRef<HTMLDivElement, SparklineCellProps>(function SparklineCell(\n { values, domain, label, ends, endsWidth, fill = false, height = 24, className, ...props },\n ref,\n) {\n const h = Math.max(0, height);\n const d = domain ? sparkPath(values, domain, h) : \"\";\n const area = fill && d ? `${d}L${VIEW_WIDTH} ${h}L0 ${h}Z` : \"\";\n return (\n <div\n ref={ref}\n data-slot=\"sparkline-cell\"\n className={cn(\"flex min-w-0 items-center gap-1.5\", className)}\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n {/*\n Both end labels are column-wide reservations (`endsWidth`), never this\n row's own text width, so every row's drawing gets the same width and the\n lines share one x scale as well as the y scale.\n */}\n {ends && (\n <span\n aria-hidden=\"true\"\n className=\"shrink-0 whitespace-nowrap text-end text-meta text-muted-foreground tabular-nums\"\n style={endsWidth?.[0] ? { width: `${endsWidth[0]}ch` } : undefined}\n >\n {ends[0]}\n </span>\n )}\n <svg\n aria-hidden=\"true\"\n data-slot=\"sparkline-cell-svg\"\n data-y-min={domain?.min}\n data-y-max={domain?.max}\n viewBox={`0 0 ${VIEW_WIDTH} ${h}`}\n preserveAspectRatio=\"none\"\n className=\"block min-w-8 flex-1 overflow-visible\"\n style={{ height: h }}\n >\n {area && <path d={area} fill={BAR_CELL_POSITIVE_COLOR} fillOpacity={0.2} stroke=\"none\" />}\n <path\n data-slot=\"sparkline-cell-line\"\n d={d}\n fill=\"none\"\n // Same mark ink as the in-cell bar: a categorical token carries no\n // contrast guarantee and read as 1.42:1 on `--card` in light (b-4).\n stroke={BAR_CELL_POSITIVE_COLOR}\n strokeWidth={SPARKLINE_CELL_STROKE_WIDTH}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n {ends && (\n <span\n aria-hidden=\"true\"\n className=\"shrink-0 whitespace-nowrap text-start text-meta text-foreground tabular-nums\"\n style={endsWidth?.[1] ? { width: `${endsWidth[1]}ch` } : undefined}\n >\n {ends[1]}\n </span>\n )}\n </div>\n );\n});\n"],"mappings":";;;;;;AAEA,SAAS,kBAAuC;AAChD,SAAS,UAAU;AA2Db,cAeA,YAfA;AAtDC,IAAM,8BAA8B;AAC3C,IAAM,aAAa;AACnB,IAAM,MAAM;AAsBZ,SAAS,UAAU,QAAoC,QAAuB,QAAgB;AAC5F,QAAM,OAAO,OAAO,MAAM,OAAO;AACjC,QAAM,OAAO,OAAO,SAAS,IAAI,cAAc,OAAO,SAAS,KAAK;AACpE,QAAM,IAAI,CAAC,MACT,OAAO,IAAI,OAAO,KAAK,IAAI,OAAO,OAAO,QAAQ,KAAK,IAAI,GAAG,SAAS,IAAI,GAAG,IAAI,SAAS;AAC5F,MAAI,IAAI;AACR,MAAI,OAAO;AACX,SAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,QAAI,MAAM,KAAM,QAAO,MAAM,OAAO;AACpC,SAAK,GAAG,OAAO,MAAM,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnE,WAAO;AAAA,EACT,CAAC;AACD,SAAO;AACT;AAGO,IAAM,gBAAgB,WAA+C,SAASA,eACnF,EAAE,QAAQ,QAAQ,OAAO,MAAM,WAAW,OAAO,OAAO,SAAS,IAAI,WAAW,GAAG,MAAM,GACzF,KACA;AACA,QAAM,IAAI,KAAK,IAAI,GAAG,MAAM;AAC5B,QAAM,IAAI,SAAS,UAAU,QAAQ,QAAQ,CAAC,IAAI;AAClD,QAAM,OAAO,QAAQ,IAAI,GAAG,CAAC,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM;AAC7D,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW,GAAG,qCAAqC,SAAS;AAAA,MAC3D,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,WAAW,iBAAM;AAAA,QAMhC,QACC;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAU;AAAA,YACV,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,KAAK,IAAI;AAAA,YAExD,eAAK,CAAC;AAAA;AAAA,QACT;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,aAAU;AAAA,YACV,cAAY,QAAQ;AAAA,YACpB,cAAY,QAAQ;AAAA,YACpB,SAAS,OAAO,UAAU,IAAI,CAAC;AAAA,YAC/B,qBAAoB;AAAA,YACpB,WAAU;AAAA,YACV,OAAO,EAAE,QAAQ,EAAE;AAAA,YAElB;AAAA,sBAAQ,oBAAC,UAAK,GAAG,MAAM,MAAM,yBAAyB,aAAa,KAAK,QAAO,QAAO;AAAA,cACvF;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV;AAAA,kBACA,MAAK;AAAA,kBAGL,QAAQ;AAAA,kBACR,aAAa;AAAA,kBACb,gBAAe;AAAA,kBACf,eAAc;AAAA,kBACd,cAAa;AAAA;AAAA,cACf;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QACC;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAU;AAAA,YACV,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,KAAK,IAAI;AAAA,YAExD,eAAK,CAAC;AAAA;AAAA,QACT;AAAA;AAAA;AAAA,EAEJ;AAEJ,CAAC;","names":["SparklineCell"]}