@undevy-org/tablewright 0.7.10 → 0.8.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.
@@ -0,0 +1,48 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { FileDown } from "lucide-react";
3
+ import { resolveCsvExportLabel, resolveExportColumnsFromTable, exportTableToCsv, downloadCsvFile, resolveTableExportButtonSize, resolveTableExportButtonVariant, defaultTableRowCellText } from "../../lib/export-table-csv.js";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Button } from "../ui/button.js";
6
+ function TableCsvExportButton({
7
+ table,
8
+ filename,
9
+ getCellText,
10
+ utf8Bom,
11
+ columnIds,
12
+ labels,
13
+ className,
14
+ variant,
15
+ size
16
+ }) {
17
+ const buttonVariant = resolveTableExportButtonVariant(variant);
18
+ const buttonSize = resolveTableExportButtonSize(size);
19
+ const exportLabel = resolveCsvExportLabel(labels);
20
+ const handleExport = () => {
21
+ const columns = resolveExportColumnsFromTable(table, columnIds, labels?.columns);
22
+ const rows = table.getRowModel().rows.map((row) => row.original);
23
+ const resolveCellText = getCellText ?? ((row, columnId) => defaultTableRowCellText(row, columnId, table));
24
+ const csv = exportTableToCsv({
25
+ columns,
26
+ rows,
27
+ getCellText: resolveCellText
28
+ });
29
+ downloadCsvFile(csv, filename, { utf8Bom });
30
+ };
31
+ return /* @__PURE__ */ jsxs(
32
+ Button,
33
+ {
34
+ type: "button",
35
+ variant: buttonVariant,
36
+ size: buttonSize,
37
+ className: cn("shrink-0", className),
38
+ onClick: handleExport,
39
+ children: [
40
+ /* @__PURE__ */ jsx(FileDown, { className: "h-4 w-4" }),
41
+ exportLabel
42
+ ]
43
+ }
44
+ );
45
+ }
46
+ export {
47
+ TableCsvExportButton
48
+ };
package/dist/es/index.js CHANGED
@@ -32,6 +32,7 @@ import { RowActionsMenu } from "./components/data-table/RowActionsMenu.js";
32
32
  import { RowControlCell, RowControlHeader } from "./components/data-table/RowControlCell.js";
33
33
  import { SortableHeader } from "./components/data-table/SortableHeader.js";
34
34
  import { TableFooter } from "./components/data-table/TableFooter.js";
35
+ import { TableCsvExportButton } from "./components/data-table/TableCsvExportButton.js";
35
36
  import { TableLayout } from "./components/data-table/TableLayout.js";
36
37
  import { useColumnResize } from "./components/data-table/hooks/use-column-resize.js";
37
38
  import { getVisiblePageNumbers } from "./components/data-table/hooks/use-table-pagination.js";
@@ -65,6 +66,7 @@ import { getCompoundValue, getDateRange, getNumberRange, getStringArray, isFilte
65
66
  import { useTableOrchestration } from "./hooks/useTableOrchestration.js";
66
67
  import { createTableOrchestrationStore, defaultValueForFilter } from "./hooks/table-orchestration-store.js";
67
68
  import { useRowDragOverride } from "./hooks/useRowDragOverride.js";
69
+ import { downloadCsvFile, exportTableToCsv } from "./lib/export-table-csv.js";
68
70
  import { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
69
71
  import { computeColumnStats } from "./lib/filter-stats.js";
70
72
  import { formatEnumLabel } from "./lib/format-enum.js";
@@ -162,6 +164,7 @@ export {
162
164
  SidebarAccountMenu,
163
165
  SidebarUser,
164
166
  SortableHeader,
167
+ TableCsvExportButton,
165
168
  TableFooter,
166
169
  TableLayout,
167
170
  Tabs,
@@ -183,6 +186,8 @@ export {
183
186
  createTableOrchestrationStore,
184
187
  defaultValueForFilter,
185
188
  displayTimeZone,
189
+ downloadCsvFile,
190
+ exportTableToCsv,
186
191
  formatDateParts,
187
192
  formatEnumLabel,
188
193
  formatLiveTimestamp,
@@ -0,0 +1,82 @@
1
+ const DEFAULT_CSV_EXPORT_LABEL = "Export CSV";
2
+ function resolveCsvExportLabel(labels) {
3
+ return labels?.export ?? DEFAULT_CSV_EXPORT_LABEL;
4
+ }
5
+ function resolveTableExportButtonSize(size) {
6
+ return size ?? "default";
7
+ }
8
+ function resolveTableExportButtonVariant(variant) {
9
+ return variant ?? "secondary";
10
+ }
11
+ function escapeCsvField(value) {
12
+ if (/[",\r\n]/.test(value)) {
13
+ return `"${value.replace(/"/g, '""')}"`;
14
+ }
15
+ return value;
16
+ }
17
+ function exportTableToCsv(options) {
18
+ const { columns, rows, getCellText } = options;
19
+ const headerLine = columns.map((column) => escapeCsvField(column.header)).join(",");
20
+ const bodyLines = rows.map(
21
+ (row) => columns.map((column) => escapeCsvField(getCellText(row, column.id))).join(",")
22
+ );
23
+ return [headerLine, ...bodyLines].join("\r\n");
24
+ }
25
+ function downloadCsvFile(content, filename, options) {
26
+ const utf8Bom = options?.utf8Bom ?? false;
27
+ const payload = utf8Bom ? `\uFEFF${content}` : content;
28
+ const blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
29
+ const url = URL.createObjectURL(blob);
30
+ const anchor = document.createElement("a");
31
+ anchor.href = url;
32
+ anchor.download = filename.endsWith(".csv") ? filename : `${filename}.csv`;
33
+ anchor.rel = "noopener";
34
+ anchor.style.display = "none";
35
+ document.body.appendChild(anchor);
36
+ anchor.click();
37
+ document.body.removeChild(anchor);
38
+ URL.revokeObjectURL(url);
39
+ }
40
+ function resolveColumnHeader(table, columnId, columnLabels) {
41
+ if (columnLabels?.[columnId]) {
42
+ return columnLabels[columnId];
43
+ }
44
+ const column = table.getColumn(columnId);
45
+ if (!column) {
46
+ return columnId;
47
+ }
48
+ const header = column.columnDef.header;
49
+ return typeof header === "string" ? header : columnId;
50
+ }
51
+ function resolveExportColumnsFromTable(table, columnIds, columnLabels) {
52
+ const ids = columnIds ?? table.getVisibleLeafColumns().map((column) => column.id);
53
+ return ids.map((id) => ({
54
+ id,
55
+ header: resolveColumnHeader(table, id, columnLabels)
56
+ }));
57
+ }
58
+ function defaultTableRowCellText(row, columnId, table) {
59
+ const tableRow = table.getRowModel().rows.find((candidate) => candidate.original === row);
60
+ if (!tableRow) {
61
+ return "";
62
+ }
63
+ const cell = tableRow.getAllCells().find((candidate) => candidate.column.id === columnId);
64
+ if (!cell) {
65
+ return "";
66
+ }
67
+ const value = cell.getValue();
68
+ if (value == null) {
69
+ return "";
70
+ }
71
+ return String(value);
72
+ }
73
+ export {
74
+ DEFAULT_CSV_EXPORT_LABEL,
75
+ defaultTableRowCellText,
76
+ downloadCsvFile,
77
+ exportTableToCsv,
78
+ resolveCsvExportLabel,
79
+ resolveExportColumnsFromTable,
80
+ resolveTableExportButtonSize,
81
+ resolveTableExportButtonVariant
82
+ };
@@ -1814,6 +1814,118 @@ function renderNumericPager(props) {
1814
1814
  )
1815
1815
  ] });
1816
1816
  }
1817
+ const DEFAULT_CSV_EXPORT_LABEL = "Export CSV";
1818
+ function resolveCsvExportLabel(labels) {
1819
+ return labels?.export ?? DEFAULT_CSV_EXPORT_LABEL;
1820
+ }
1821
+ function resolveTableExportButtonSize(size) {
1822
+ return size ?? "default";
1823
+ }
1824
+ function resolveTableExportButtonVariant(variant) {
1825
+ return variant ?? "secondary";
1826
+ }
1827
+ function escapeCsvField(value) {
1828
+ if (/[",\r\n]/.test(value)) {
1829
+ return `"${value.replace(/"/g, '""')}"`;
1830
+ }
1831
+ return value;
1832
+ }
1833
+ function exportTableToCsv(options) {
1834
+ const { columns, rows, getCellText } = options;
1835
+ const headerLine = columns.map((column) => escapeCsvField(column.header)).join(",");
1836
+ const bodyLines = rows.map(
1837
+ (row) => columns.map((column) => escapeCsvField(getCellText(row, column.id))).join(",")
1838
+ );
1839
+ return [headerLine, ...bodyLines].join("\r\n");
1840
+ }
1841
+ function downloadCsvFile(content, filename, options) {
1842
+ const utf8Bom = options?.utf8Bom ?? false;
1843
+ const payload = utf8Bom ? `\uFEFF${content}` : content;
1844
+ const blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
1845
+ const url = URL.createObjectURL(blob);
1846
+ const anchor = document.createElement("a");
1847
+ anchor.href = url;
1848
+ anchor.download = filename.endsWith(".csv") ? filename : `${filename}.csv`;
1849
+ anchor.rel = "noopener";
1850
+ anchor.style.display = "none";
1851
+ document.body.appendChild(anchor);
1852
+ anchor.click();
1853
+ document.body.removeChild(anchor);
1854
+ URL.revokeObjectURL(url);
1855
+ }
1856
+ function resolveColumnHeader(table, columnId, columnLabels) {
1857
+ if (columnLabels?.[columnId]) {
1858
+ return columnLabels[columnId];
1859
+ }
1860
+ const column = table.getColumn(columnId);
1861
+ if (!column) {
1862
+ return columnId;
1863
+ }
1864
+ const header = column.columnDef.header;
1865
+ return typeof header === "string" ? header : columnId;
1866
+ }
1867
+ function resolveExportColumnsFromTable(table, columnIds, columnLabels) {
1868
+ const ids = columnIds ?? table.getVisibleLeafColumns().map((column) => column.id);
1869
+ return ids.map((id) => ({
1870
+ id,
1871
+ header: resolveColumnHeader(table, id, columnLabels)
1872
+ }));
1873
+ }
1874
+ function defaultTableRowCellText(row, columnId, table) {
1875
+ const tableRow = table.getRowModel().rows.find((candidate) => candidate.original === row);
1876
+ if (!tableRow) {
1877
+ return "";
1878
+ }
1879
+ const cell = tableRow.getAllCells().find((candidate) => candidate.column.id === columnId);
1880
+ if (!cell) {
1881
+ return "";
1882
+ }
1883
+ const value = cell.getValue();
1884
+ if (value == null) {
1885
+ return "";
1886
+ }
1887
+ return String(value);
1888
+ }
1889
+ function TableCsvExportButton({
1890
+ table,
1891
+ filename,
1892
+ getCellText,
1893
+ utf8Bom,
1894
+ columnIds,
1895
+ labels,
1896
+ className,
1897
+ variant,
1898
+ size
1899
+ }) {
1900
+ const buttonVariant = resolveTableExportButtonVariant(variant);
1901
+ const buttonSize = resolveTableExportButtonSize(size);
1902
+ const exportLabel = resolveCsvExportLabel(labels);
1903
+ const handleExport = () => {
1904
+ const columns = resolveExportColumnsFromTable(table, columnIds, labels?.columns);
1905
+ const rows = table.getRowModel().rows.map((row) => row.original);
1906
+ const resolveCellText = getCellText ?? ((row, columnId) => defaultTableRowCellText(row, columnId, table));
1907
+ const csv = exportTableToCsv({
1908
+ columns,
1909
+ rows,
1910
+ getCellText: resolveCellText
1911
+ });
1912
+ downloadCsvFile(csv, filename, { utf8Bom });
1913
+ };
1914
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1915
+ Button,
1916
+ {
1917
+ type: "button",
1918
+ variant: buttonVariant,
1919
+ size: buttonSize,
1920
+ className: cn("shrink-0", className),
1921
+ onClick: handleExport,
1922
+ children: [
1923
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { className: "h-4 w-4" }),
1924
+ exportLabel
1925
+ ]
1926
+ }
1927
+ );
1928
+ }
1817
1929
  function useColumnResize(defaults, initialWidths) {
1818
1930
  const [widths, setWidths] = React.useState(() => {
1819
1931
  const w = {};
@@ -5943,6 +6055,7 @@ exports.Sidebar = Sidebar;
5943
6055
  exports.SidebarAccountMenu = SidebarAccountMenu;
5944
6056
  exports.SidebarUser = SidebarUser;
5945
6057
  exports.SortableHeader = SortableHeader;
6058
+ exports.TableCsvExportButton = TableCsvExportButton;
5946
6059
  exports.TableFooter = TableFooter;
5947
6060
  exports.TableLayout = TableLayout;
5948
6061
  exports.Tabs = Tabs;
@@ -5964,6 +6077,8 @@ exports.computeColumnStats = computeColumnStats;
5964
6077
  exports.createTableOrchestrationStore = createTableOrchestrationStore;
5965
6078
  exports.defaultValueForFilter = defaultValueForFilter;
5966
6079
  exports.displayTimeZone = displayTimeZone;
6080
+ exports.downloadCsvFile = downloadCsvFile;
6081
+ exports.exportTableToCsv = exportTableToCsv;
5967
6082
  exports.formatDateParts = formatDateParts;
5968
6083
  exports.formatEnumLabel = formatEnumLabel;
5969
6084
  exports.formatLiveTimestamp = formatLiveTimestamp;
@@ -0,0 +1,20 @@
1
+ import type { Table } from "@tanstack/react-table";
2
+ import { type ButtonProps } from "../ui/button.js";
3
+ export type TableCsvExportButtonLabels = {
4
+ /** @default "Export CSV" */
5
+ export?: string;
6
+ columns?: Record<string, string>;
7
+ };
8
+ export type TableCsvExportButtonProps<TData> = {
9
+ table: Table<TData>;
10
+ filename: string;
11
+ getCellText?: (row: TData, columnId: string) => string;
12
+ utf8Bom?: boolean;
13
+ /** Defaults to visible leaf columns on the table instance. */
14
+ columnIds?: string[];
15
+ labels?: TableCsvExportButtonLabels;
16
+ className?: string;
17
+ variant?: ButtonProps["variant"];
18
+ size?: ButtonProps["size"];
19
+ };
20
+ export declare function TableCsvExportButton<TData>({ table, filename, getCellText, utf8Bom, columnIds, labels, className, variant, size, }: TableCsvExportButtonProps<TData>): import("react").JSX.Element;
@@ -46,6 +46,8 @@ export { RowControlHeader } from "./components/data-table/RowControlCell.js";
46
46
  export type { RowControlHeaderProps } from "./components/data-table/RowControlCell.js";
47
47
  export { SortableHeader } from "./components/data-table/SortableHeader.js";
48
48
  export { TableFooter } from "./components/data-table/TableFooter.js";
49
+ export { TableCsvExportButton } from "./components/data-table/TableCsvExportButton.js";
50
+ export type { TableCsvExportButtonProps } from "./components/data-table/TableCsvExportButton.js";
49
51
  export { TableLayout } from "./components/data-table/TableLayout.js";
50
52
  export { useColumnResize } from "./components/data-table/hooks/use-column-resize.js";
51
53
  export { getVisiblePageNumbers } from "./components/data-table/hooks/use-table-pagination.js";
@@ -100,6 +102,8 @@ export { createTableOrchestrationStore, defaultValueForFilter, } from "./hooks/t
100
102
  export type { TableOrchestrationState, TableOrchestrationActions, TableOrchestrationStore, TableOrchestrationStoreApi, } from "./hooks/table-orchestration-store.js";
101
103
  export { useRowDragOverride } from "./hooks/useRowDragOverride.js";
102
104
  export type { UseRowDragOverrideResult } from "./hooks/useRowDragOverride.js";
105
+ export { downloadCsvFile, exportTableToCsv, } from "./lib/export-table-csv.js";
106
+ export type { DownloadCsvFileOptions, ExportTableToCsvOptions, } from "./lib/export-table-csv.js";
103
107
  export { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
104
108
  export { computeColumnStats } from "./lib/filter-stats.js";
105
109
  export { formatEnumLabel } from "./lib/format-enum.js";
@@ -0,0 +1,26 @@
1
+ import type { Table } from "@tanstack/react-table";
2
+ export type ExportTableToCsvOptions<TRow> = {
3
+ columns: {
4
+ id: string;
5
+ header: string;
6
+ }[];
7
+ rows: TRow[];
8
+ getCellText: (row: TRow, columnId: string) => string;
9
+ };
10
+ export declare const DEFAULT_CSV_EXPORT_LABEL = "Export CSV";
11
+ export declare function resolveCsvExportLabel(labels?: {
12
+ export?: string;
13
+ }): string;
14
+ export declare function resolveTableExportButtonSize<TSize extends string | null | undefined>(size: TSize): NonNullable<TSize> | "default";
15
+ export declare function resolveTableExportButtonVariant<TVariant extends string | null | undefined>(variant: TVariant): NonNullable<TVariant> | "secondary";
16
+ export type DownloadCsvFileOptions = {
17
+ /** Prepends a UTF-8 BOM so Excel recognizes UTF-8. @default false */
18
+ utf8Bom?: boolean;
19
+ };
20
+ export declare function exportTableToCsv<TRow>(options: ExportTableToCsvOptions<TRow>): string;
21
+ export declare function downloadCsvFile(content: string, filename: string, options?: DownloadCsvFileOptions): void;
22
+ export declare function resolveExportColumnsFromTable<TData>(table: Table<TData>, columnIds?: string[], columnLabels?: Record<string, string>): {
23
+ id: string;
24
+ header: string;
25
+ }[];
26
+ export declare function defaultTableRowCellText<TData>(row: TData, columnId: string, table: Table<TData>): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undevy-org/tablewright",
3
- "version": "0.7.10",
3
+ "version": "0.8.0",
4
4
  "description": "React data-table and application-shell components: filters, drawers, row controls, sortable and resizable columns, built on Radix UI and TanStack Table.",
5
5
  "license": "MIT",
6
6
  "repository": {