tabler-react-2 0.1.153-alpha.2 → 0.1.154

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
@@ -156,7 +156,7 @@ Object.keys(_index15).forEach(function (key) {
156
156
  }
157
157
  });
158
158
  });
159
- var _index16 = require("./timeline/index");
159
+ var _index16 = require("./table-v2/index");
160
160
  Object.keys(_index16).forEach(function (key) {
161
161
  if (key === "default" || key === "__esModule") return;
162
162
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -168,7 +168,7 @@ Object.keys(_index16).forEach(function (key) {
168
168
  }
169
169
  });
170
170
  });
171
- var _index17 = require("./input/index");
171
+ var _index17 = require("./timeline/index");
172
172
  Object.keys(_index17).forEach(function (key) {
173
173
  if (key === "default" || key === "__esModule") return;
174
174
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -180,7 +180,7 @@ Object.keys(_index17).forEach(function (key) {
180
180
  }
181
181
  });
182
182
  });
183
- var _index18 = require("./navbar/index");
183
+ var _index18 = require("./input/index");
184
184
  Object.keys(_index18).forEach(function (key) {
185
185
  if (key === "default" || key === "__esModule") return;
186
186
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -192,7 +192,7 @@ Object.keys(_index18).forEach(function (key) {
192
192
  }
193
193
  });
194
194
  });
195
- var _index19 = require("./segmentedControl/index");
195
+ var _index19 = require("./navbar/index");
196
196
  Object.keys(_index19).forEach(function (key) {
197
197
  if (key === "default" || key === "__esModule") return;
198
198
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -204,7 +204,7 @@ Object.keys(_index19).forEach(function (key) {
204
204
  }
205
205
  });
206
206
  });
207
- var _index20 = require("./offcanvas/index");
207
+ var _index20 = require("./segmentedControl/index");
208
208
  Object.keys(_index20).forEach(function (key) {
209
209
  if (key === "default" || key === "__esModule") return;
210
210
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -216,5 +216,17 @@ Object.keys(_index20).forEach(function (key) {
216
216
  }
217
217
  });
218
218
  });
219
+ var _index21 = require("./offcanvas/index");
220
+ Object.keys(_index21).forEach(function (key) {
221
+ if (key === "default" || key === "__esModule") return;
222
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
223
+ if (key in exports && exports[key] === _index21[key]) return;
224
+ Object.defineProperty(exports, key, {
225
+ enumerable: true,
226
+ get: function get() {
227
+ return _index21[key];
228
+ }
229
+ });
230
+ });
219
231
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
220
232
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.TableV2 = exports.SortIcon = exports.RangeText = exports.Pager = exports.PageSizeSelect = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _propTypes = _interopRequireDefault(require("prop-types"));
10
+ var _reactTable = require("@tanstack/react-table");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
14
+ // TableV2.jsx
15
+ // TanStack Table v8 styled like the Original component's Tabler look.
16
+ // Adds `dense` mode for a compact layout.
17
+ // Controlled (server-side) pagination & sorting. ES modules, named exports, arrow functions only.
18
+
19
+ /** Small utilities */
20
+
21
+ var SortIcon = exports.SortIcon = function SortIcon(_ref) {
22
+ var state = _ref.state;
23
+ if (state === "asc") return /*#__PURE__*/_react["default"].createElement("span", {
24
+ "aria-label": "sorted ascending"
25
+ }, "\u25B2");
26
+ if (state === "desc") return /*#__PURE__*/_react["default"].createElement("span", {
27
+ "aria-label": "sorted descending"
28
+ }, "\u25BC");
29
+ return /*#__PURE__*/_react["default"].createElement("span", {
30
+ className: "text-muted",
31
+ "aria-hidden": true
32
+ }, "\u2195");
33
+ };
34
+ var RangeText = exports.RangeText = function RangeText(_ref2) {
35
+ var page = _ref2.page,
36
+ size = _ref2.size,
37
+ total = _ref2.total,
38
+ dense = _ref2.dense;
39
+ var start = total === 0 ? 0 : (page - 1) * size + 1;
40
+ var end = Math.min(page * size, total);
41
+ return /*#__PURE__*/_react["default"].createElement("span", {
42
+ className: dense ? "ms-2 small text-muted" : "ms-3 text-muted"
43
+ }, start.toLocaleString(), "\u2013", end.toLocaleString(), " of", " ", total.toLocaleString());
44
+ };
45
+ var PageSizeSelect = exports.PageSizeSelect = function PageSizeSelect(_ref3) {
46
+ var value = _ref3.value,
47
+ _onChange = _ref3.onChange,
48
+ _ref3$options = _ref3.options,
49
+ options = _ref3$options === void 0 ? [10, 25, 50, 100] : _ref3$options,
50
+ dense = _ref3.dense;
51
+ return /*#__PURE__*/_react["default"].createElement("select", {
52
+ className: dense ? "form-select form-select-sm" : "form-select",
53
+ value: value,
54
+ onChange: function onChange(e) {
55
+ return _onChange(Number(e.target.value));
56
+ },
57
+ "aria-label": "Rows per page",
58
+ style: {
59
+ width: dense ? 90 : 110
60
+ }
61
+ }, options.map(function (n) {
62
+ return /*#__PURE__*/_react["default"].createElement("option", {
63
+ key: n,
64
+ value: n
65
+ }, n, " / page");
66
+ }));
67
+ };
68
+ var Pager = exports.Pager = function Pager(_ref4) {
69
+ var page = _ref4.page,
70
+ size = _ref4.size,
71
+ total = _ref4.total,
72
+ onPageChange = _ref4.onPageChange,
73
+ disabled = _ref4.disabled,
74
+ _ref4$className = _ref4.className,
75
+ className = _ref4$className === void 0 ? "" : _ref4$className,
76
+ dense = _ref4.dense;
77
+ var totalPages = Math.max(1, Math.ceil(total / Math.max(1, size)));
78
+ var canPrev = page > 1 && !disabled;
79
+ var canNext = page < totalPages && !disabled;
80
+ var go = function go(p) {
81
+ var clamped = Math.min(Math.max(1, p), totalPages);
82
+ if (clamped !== page) onPageChange(clamped);
83
+ };
84
+ return /*#__PURE__*/_react["default"].createElement("div", {
85
+ className: "d-inline-flex align-items-center ".concat(dense ? "gap-2" : "", " ").concat(className)
86
+ }, /*#__PURE__*/_react["default"].createElement("button", {
87
+ className: "btn ".concat(dense ? "btn-sm" : "btn-sm", " ").concat(dense ? "px-2 py-1" : ""),
88
+ onClick: function onClick() {
89
+ return go(Math.max(1, page - 1));
90
+ },
91
+ disabled: !canPrev
92
+ }, "Previous"), /*#__PURE__*/_react["default"].createElement("button", {
93
+ className: "btn ".concat(dense ? "btn-sm ms-1" : "btn-sm ms-2", " ").concat(dense ? "px-2 py-1" : ""),
94
+ onClick: function onClick() {
95
+ return go(Math.min(totalPages, page + 1));
96
+ },
97
+ disabled: !canNext
98
+ }, "Next"), /*#__PURE__*/_react["default"].createElement("span", {
99
+ className: dense ? "ms-2 small text-muted" : "ms-3 text-muted"
100
+ }, "Page ", page, " of ", totalPages));
101
+ };
102
+
103
+ /** Main table */
104
+
105
+ var TableV2 = exports.TableV2 = function TableV2(_ref5) {
106
+ var columns = _ref5.columns,
107
+ data = _ref5.data,
108
+ totalRows = _ref5.totalRows,
109
+ page = _ref5.page,
110
+ size = _ref5.size,
111
+ onPageChange = _ref5.onPageChange,
112
+ onSizeChange = _ref5.onSizeChange,
113
+ sorting = _ref5.sorting,
114
+ onSortingChange = _ref5.onSortingChange,
115
+ getRowId = _ref5.getRowId,
116
+ rowSelection = _ref5.rowSelection,
117
+ onRowSelectionChange = _ref5.onRowSelectionChange,
118
+ _ref5$nowrap = _ref5.nowrap,
119
+ nowrap = _ref5$nowrap === void 0 ? false : _ref5$nowrap,
120
+ _ref5$stickyHeader = _ref5.stickyHeader,
121
+ stickyHeader = _ref5$stickyHeader === void 0 ? false : _ref5$stickyHeader,
122
+ _ref5$className = _ref5.className,
123
+ className = _ref5$className === void 0 ? "" : _ref5$className,
124
+ _ref5$tableClassName = _ref5.tableClassName,
125
+ tableClassName = _ref5$tableClassName === void 0 ? "" : _ref5$tableClassName,
126
+ _ref5$paginationClass = _ref5.paginationClassName,
127
+ paginationClassName = _ref5$paginationClass === void 0 ? "" : _ref5$paginationClass,
128
+ _ref5$parentClassName = _ref5.parentClassName,
129
+ parentClassName = _ref5$parentClassName === void 0 ? "" : _ref5$parentClassName,
130
+ _ref5$dense = _ref5.dense,
131
+ dense = _ref5$dense === void 0 ? false : _ref5$dense,
132
+ _ref5$emptyState = _ref5.emptyState,
133
+ emptyState = _ref5$emptyState === void 0 ? "No data" : _ref5$emptyState,
134
+ _ref5$loading = _ref5.loading,
135
+ loading = _ref5$loading === void 0 ? false : _ref5$loading,
136
+ _ref5$showPagination = _ref5.showPagination,
137
+ showPagination = _ref5$showPagination === void 0 ? true : _ref5$showPagination,
138
+ _ref5$pageSizeOptions = _ref5.pageSizeOptions,
139
+ pageSizeOptions = _ref5$pageSizeOptions === void 0 ? [10, 25, 50, 100] : _ref5$pageSizeOptions;
140
+ var table = (0, _reactTable.useReactTable)({
141
+ data: data,
142
+ columns: columns,
143
+ state: {
144
+ sorting: sorting,
145
+ rowSelection: rowSelection !== null && rowSelection !== void 0 ? rowSelection : {}
146
+ },
147
+ getCoreRowModel: (0, _reactTable.getCoreRowModel)(),
148
+ manualSorting: true,
149
+ enableSortingRemoval: false,
150
+ onSortingChange: onSortingChange,
151
+ getRowId: getRowId,
152
+ onRowSelectionChange: onRowSelectionChange
153
+ });
154
+ var headerCell = function headerCell(_ref6) {
155
+ var header = _ref6.header;
156
+ var canSort = header.column.getCanSort();
157
+ var sortState = header.column.getIsSorted();
158
+ var handleSort = function handleSort() {
159
+ if (!canSort) return;
160
+ var id = header.column.id;
161
+ var next = sortState === "asc" ? [{
162
+ id: id,
163
+ desc: true
164
+ }] : sortState === "desc" ? [] : [{
165
+ id: id,
166
+ desc: false
167
+ }];
168
+ onSortingChange(next);
169
+ };
170
+ var thClass = [header.column.columnDef.className || "", canSort ? "sortable" : "", dense ? "py-1" : ""].filter(Boolean).join(" ");
171
+ return /*#__PURE__*/_react["default"].createElement("th", {
172
+ key: header.id,
173
+ className: thClass,
174
+ onClick: canSort && !loading ? handleSort : undefined,
175
+ style: {
176
+ cursor: canSort && !loading ? "pointer" : "default"
177
+ },
178
+ "aria-sort": sortState === "asc" ? "ascending" : sortState === "desc" ? "descending" : "none"
179
+ }, /*#__PURE__*/_react["default"].createElement("span", {
180
+ style: {
181
+ marginRight: 8
182
+ }
183
+ }, (0, _reactTable.flexRender)(header.column.columnDef.header, header.getContext())), canSort && /*#__PURE__*/_react["default"].createElement(SortIcon, {
184
+ state: sortState || false
185
+ }));
186
+ };
187
+ var content = (0, _react.useMemo)(function () {
188
+ if (!loading && data.length === 0) {
189
+ return /*#__PURE__*/_react["default"].createElement("tr", null, /*#__PURE__*/_react["default"].createElement("td", {
190
+ colSpan: table.getAllLeafColumns().length,
191
+ className: "text-center ".concat(dense ? "py-3 small" : "py-3", " text-muted")
192
+ }, typeof emptyState === "function" ? emptyState() : emptyState));
193
+ }
194
+ return table.getRowModel().rows.map(function (row) {
195
+ return /*#__PURE__*/_react["default"].createElement("tr", {
196
+ key: row.id,
197
+ className: [row.getIsSelected() ? "table-active" : "", dense ? "align-middle" : ""].filter(Boolean).join(" ")
198
+ }, row.getVisibleCells().map(function (cell) {
199
+ return /*#__PURE__*/_react["default"].createElement("td", {
200
+ key: cell.id,
201
+ className: [cell.column.columnDef.className || "", dense ? "py-1" : ""].filter(Boolean).join(" ")
202
+ }, (0, _reactTable.flexRender)(cell.column.columnDef.cell, cell.getContext()));
203
+ }));
204
+ });
205
+ }, [data, loading, emptyState, table, dense]);
206
+ var total = totalRows !== null && totalRows !== void 0 ? totalRows : 0;
207
+ return /*#__PURE__*/_react["default"].createElement("div", {
208
+ className: parentClassName
209
+ }, /*#__PURE__*/_react["default"].createElement("div", {
210
+ className: "table-responsive ".concat(nowrap ? "table-nowrap" : "", " ").concat(className)
211
+ }, /*#__PURE__*/_react["default"].createElement("table", {
212
+ className: ["table table-vcenter", stickyHeader ? "sticky-top" : "", dense ? "table-sm" : "", tableClassName].filter(Boolean).join(" ")
213
+ }, /*#__PURE__*/_react["default"].createElement("thead", null, table.getHeaderGroups().map(function (hg) {
214
+ return /*#__PURE__*/_react["default"].createElement("tr", {
215
+ key: hg.id
216
+ }, hg.headers.map(function (h) {
217
+ return headerCell({
218
+ header: h
219
+ });
220
+ }));
221
+ })), /*#__PURE__*/_react["default"].createElement("tbody", null, loading && /*#__PURE__*/_react["default"].createElement("tr", null, /*#__PURE__*/_react["default"].createElement("td", {
222
+ colSpan: table.getAllLeafColumns().length,
223
+ className: "text-center ".concat(dense ? "py-2" : "py-3")
224
+ }, /*#__PURE__*/_react["default"].createElement("span", {
225
+ className: "spinner-border spinner-border-sm me-2",
226
+ role: "status",
227
+ "aria-hidden": true
228
+ }), /*#__PURE__*/_react["default"].createElement("span", {
229
+ className: dense ? "small text-muted" : "text-muted"
230
+ }, "Loading\u2026"))), content))), showPagination && /*#__PURE__*/_react["default"].createElement("div", {
231
+ className: "d-flex justify-content-between align-items-center ".concat(dense ? "mt-1" : "mt-2", " ").concat(paginationClassName)
232
+ }, /*#__PURE__*/_react["default"].createElement("div", {
233
+ className: "d-flex align-items-center ".concat(dense ? "gap-2" : "")
234
+ }, /*#__PURE__*/_react["default"].createElement(Pager, {
235
+ page: page,
236
+ size: size,
237
+ total: total,
238
+ onPageChange: onPageChange,
239
+ disabled: loading,
240
+ dense: dense
241
+ }), /*#__PURE__*/_react["default"].createElement(RangeText, {
242
+ page: page,
243
+ size: size,
244
+ total: total,
245
+ dense: dense
246
+ })), /*#__PURE__*/_react["default"].createElement(PageSizeSelect, {
247
+ value: size,
248
+ onChange: function onChange(n) {
249
+ onSizeChange === null || onSizeChange === void 0 || onSizeChange(n);
250
+ },
251
+ options: pageSizeOptions,
252
+ dense: dense
253
+ })));
254
+ };
255
+
256
+ /** PropTypes */
257
+
258
+ SortIcon.propTypes = {
259
+ state: _propTypes["default"].oneOf(["asc", "desc", false])
260
+ };
261
+ RangeText.propTypes = {
262
+ page: _propTypes["default"].number.isRequired,
263
+ size: _propTypes["default"].number.isRequired,
264
+ total: _propTypes["default"].number.isRequired,
265
+ dense: _propTypes["default"].bool
266
+ };
267
+ PageSizeSelect.propTypes = {
268
+ value: _propTypes["default"].number.isRequired,
269
+ onChange: _propTypes["default"].func.isRequired,
270
+ options: _propTypes["default"].arrayOf(_propTypes["default"].number),
271
+ dense: _propTypes["default"].bool
272
+ };
273
+ Pager.propTypes = {
274
+ page: _propTypes["default"].number.isRequired,
275
+ size: _propTypes["default"].number.isRequired,
276
+ total: _propTypes["default"].number.isRequired,
277
+ onPageChange: _propTypes["default"].func.isRequired,
278
+ disabled: _propTypes["default"].bool,
279
+ className: _propTypes["default"].string,
280
+ dense: _propTypes["default"].bool
281
+ };
282
+ TableV2.propTypes = {
283
+ columns: _propTypes["default"].array.isRequired,
284
+ data: _propTypes["default"].array.isRequired,
285
+ totalRows: _propTypes["default"].number.isRequired,
286
+ page: _propTypes["default"].number.isRequired,
287
+ size: _propTypes["default"].number.isRequired,
288
+ onPageChange: _propTypes["default"].func.isRequired,
289
+ onSizeChange: _propTypes["default"].func.isRequired,
290
+ sorting: _propTypes["default"].arrayOf(_propTypes["default"].shape({
291
+ id: _propTypes["default"].string.isRequired,
292
+ desc: _propTypes["default"].bool
293
+ })).isRequired,
294
+ onSortingChange: _propTypes["default"].func.isRequired,
295
+ getRowId: _propTypes["default"].func,
296
+ rowSelection: _propTypes["default"].object,
297
+ onRowSelectionChange: _propTypes["default"].func,
298
+ nowrap: _propTypes["default"].bool,
299
+ stickyHeader: _propTypes["default"].bool,
300
+ className: _propTypes["default"].string,
301
+ tableClassName: _propTypes["default"].string,
302
+ paginationClassName: _propTypes["default"].string,
303
+ parentClassName: _propTypes["default"].string,
304
+ emptyState: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].func]),
305
+ loading: _propTypes["default"].bool,
306
+ showPagination: _propTypes["default"].bool,
307
+ pageSizeOptions: _propTypes["default"].arrayOf(_propTypes["default"].number),
308
+ dense: _propTypes["default"].bool // <<< NEW
309
+ };
310
+
311
+ /** Usage (server-side)
312
+ *
313
+ * <TableV2
314
+ * dense // compact mode
315
+ * columns={columns}
316
+ * data={data}
317
+ * totalRows={total}
318
+ * page={page}
319
+ * size={size}
320
+ * onPageChange={setPage}
321
+ * onSizeChange={(n)=>{ setPage(1); setSize(n); }}
322
+ * sorting={sorting}
323
+ * onSortingChange={(next)=>{ setPage(1); setSorting(next); }}
324
+ * loading={loading}
325
+ * nowrap
326
+ * stickyHeader
327
+ * />
328
+ */
@@ -0,0 +1,36 @@
1
+ const path = require("path");
2
+
3
+ exports.onCreateWebpackConfig = ({ stage, actions, loaders }) => {
4
+ // Only alias to source during local development unless explicitly enabled
5
+ const enableSourceAlias =
6
+ stage === "develop" || process.env.DOCS_USE_SRC === "1";
7
+
8
+ if (!enableSourceAlias) return;
9
+
10
+ actions.setWebpackConfig({
11
+ resolve: {
12
+ alias: {
13
+ // Use library source directly during docs development
14
+ "tabler-react-2": path.resolve(__dirname, "../src"),
15
+
16
+ // Ensure a single React/DOM instance (avoid hooks mismatch)
17
+ react: path.resolve(__dirname, "node_modules/react"),
18
+ "react-dom": path.resolve(__dirname, "node_modules/react-dom"),
19
+ // Route dependency to the docs install so resolution is stable
20
+ "react-router-dom": path.resolve(
21
+ __dirname,
22
+ "node_modules/react-router-dom"
23
+ ),
24
+ },
25
+ },
26
+ module: {
27
+ rules: [
28
+ {
29
+ test: /\.jsx?$/,
30
+ include: [path.resolve(__dirname, "../src")],
31
+ use: [loaders.js()],
32
+ },
33
+ ],
34
+ },
35
+ });
36
+ };
@@ -57,6 +57,9 @@ export const Table = loadable(() =>
57
57
  export const Timeline = loadable(() =>
58
58
  import("tabler-react-2").then((mod) => mod.Timeline)
59
59
  );
60
+ export const TableV2 = loadable(() =>
61
+ import("tabler-react-2").then((mod) => mod.TableV2)
62
+ );
60
63
 
61
64
  // Util
62
65
  const Hr = loadable(() => import("tabler-react-2").then((mod) => mod.Util.Hr));
@@ -1,63 +1,140 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
 
4
- export const Tabler = ({ children }) => {
5
- const containerRef = useRef(null);
6
- const shadowRef = useRef(null);
7
- const [wrapper, setWrapper] = useState(null);
4
+ const TABLER_CSS_URL =
5
+ "https://cdn.jsdelivr.net/npm/@tabler/core@1.3.0/dist/css/tabler.min.css";
6
+ const TABLER_JS_URL =
7
+ "https://cdn.jsdelivr.net/npm/@tabler/core@1.3.0/dist/js/tabler.min.js";
8
+
9
+ /**
10
+ * <Tabler>
11
+ * Same public API as before:
12
+ * - children
13
+ * - theme?: "light" | "dark" (defaults to "light")
14
+ * Also accepts className and style for the outer iframe element.
15
+ */
16
+ export const Tabler = ({
17
+ children,
18
+ theme = "light",
19
+ className,
20
+ style,
21
+ headExtras = "",
22
+ cssUrl = TABLER_CSS_URL,
23
+ jsUrl = TABLER_JS_URL,
24
+ }) => {
25
+ const iframeRef = useRef(null);
26
+ const [mountNode, setMountNode] = useState(null);
27
+ const roRef = useRef(null);
28
+
29
+ const srcDoc = useMemo(() => {
30
+ const t = theme === "dark" ? "dark" : "light";
31
+ return `<!DOCTYPE html>
32
+ <html lang="en" data-bs-theme="${t}">
33
+ <head>
34
+ <meta charset="utf-8" />
35
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
36
+ <link rel="stylesheet" href="${cssUrl}">
37
+ ${headExtras}
38
+ <style>
39
+ html, body { margin: 0; padding: 0; }
40
+ body { display: block; background-color: transparent!important; }
41
+ #app { min-height: 0; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div id="app"></div>
46
+ </body>
47
+ </html>`;
48
+ }, [cssUrl, headExtras, theme]);
8
49
 
9
50
  useEffect(() => {
10
- const initialize = async () => {
11
- if (containerRef.current && !shadowRef.current) {
12
- // Create a shadow root for this instance
13
- const shadowRoot = containerRef.current.attachShadow({ mode: "open" });
14
- shadowRef.current = shadowRoot;
15
-
16
- // Inject Tabler stylesheet
17
- const styleLink = document.createElement("link");
18
- styleLink.rel = "stylesheet";
19
- styleLink.href =
20
- "https://cdn.jsdelivr.net/npm/@tabler/core@1.3.2/dist/css/tabler.min.css";
21
-
22
- // Create a wrapper div for children
23
- const wrapperDiv = document.createElement("div");
24
- wrapperDiv.style.display = "inline-block";
25
- wrapperDiv.style.width = "auto";
26
- wrapperDiv.style.height = "auto";
27
-
28
- shadowRoot.appendChild(styleLink);
29
- shadowRoot.appendChild(wrapperDiv);
30
- setWrapper(wrapperDiv);
31
-
32
- // Load and run Tabler script in an isolated context
51
+ const iframe = iframeRef.current;
52
+ if (!iframe) return;
53
+
54
+ iframe.srcdoc = srcDoc;
55
+
56
+ const onLoad = () => {
57
+ const doc = iframe.contentDocument;
58
+ const win = iframe.contentWindow;
59
+ if (!doc || !win) return;
60
+
61
+ // Ensure theme attribute is correct
62
+ doc.documentElement.setAttribute(
63
+ "data-bs-theme",
64
+ theme === "dark" ? "dark" : "light"
65
+ );
66
+
67
+ const app = doc.getElementById("app");
68
+ setMountNode(app || null);
69
+
70
+ // Inject Tabler JS in iframe context
71
+ const s = doc.createElement("script");
72
+ s.src = jsUrl;
73
+ s.defer = true;
74
+ doc.head.appendChild(s);
75
+
76
+ // Auto-resize to content
77
+ if (roRef.current) {
33
78
  try {
34
- const response = await fetch("/tabler.min.js");
35
- const scriptText = await response.text();
36
-
37
- // Wrap the code in a function that temporarily sets window.SHADOW_DOC
38
- const runTablerScript = new Function(
39
- "shadowRoot",
40
- `
41
- window.USE_FALLBACK_ANCHOR = true;
42
- const originalShadowDoc = window.SHADOW_DOC;
43
- window.SHADOW_DOC = shadowRoot;
44
- ${scriptText}
45
- window.SHADOW_DOC = originalShadowDoc;
46
- `
47
- );
48
- runTablerScript(shadowRoot);
49
- } catch (err) {
50
- console.error("Failed to load Tabler script:", err);
51
- }
79
+ roRef.current.disconnect();
80
+ } catch {}
52
81
  }
82
+ const ro = new win.ResizeObserver(() => {
83
+ const h =
84
+ doc.documentElement.scrollHeight ||
85
+ doc.body.scrollHeight ||
86
+ app?.scrollHeight ||
87
+ 0;
88
+ // iframe.style.height = `${Math.ceil(h) + 1}px`;
89
+ });
90
+ roRef.current = ro;
91
+ ro.observe(doc.documentElement);
92
+
93
+ // Initial sizing tick
94
+ setTimeout(() => {
95
+ const h =
96
+ doc.documentElement.scrollHeight ||
97
+ doc.body.scrollHeight ||
98
+ app?.scrollHeight ||
99
+ 0;
100
+ iframe.style.height = `${Math.ceil(h) + 1}px`;
101
+ }, 0);
53
102
  };
54
103
 
55
- initialize();
56
- }, []);
104
+ iframe.addEventListener("load", onLoad);
105
+ return () => {
106
+ iframe.removeEventListener("load", onLoad);
107
+ if (roRef.current) {
108
+ try {
109
+ roRef.current.disconnect();
110
+ } catch {}
111
+ roRef.current = null;
112
+ }
113
+ setMountNode(null);
114
+ };
115
+ }, [srcDoc, jsUrl, theme]);
116
+
117
+ // Keep theme in sync if it changes
118
+ useEffect(() => {
119
+ const doc = iframeRef.current?.contentDocument;
120
+ if (doc) {
121
+ doc.documentElement.setAttribute(
122
+ "data-bs-theme",
123
+ theme === "dark" ? "dark" : "light"
124
+ );
125
+ }
126
+ }, [theme]);
57
127
 
58
128
  return (
59
- <div ref={containerRef}>
60
- {wrapper && ReactDOM.createPortal(children, wrapper)}
61
- </div>
129
+ <>
130
+ <iframe
131
+ ref={iframeRef}
132
+ className={className}
133
+ style={{ width: "100%", border: 0, display: "block", ...(style || {}) }}
134
+ // Add sandbox if you want stricter isolation; e.g.:
135
+ // sandbox="allow-scripts allow-same-origin"
136
+ />
137
+ {mountNode ? ReactDOM.createPortal(children, mountNode) : null}
138
+ </>
62
139
  );
63
140
  };
@@ -50,6 +50,8 @@
50
50
  link: "/components/steps"
51
51
  - label: "Tables"
52
52
  link: "/components/tables"
53
+ - label: "Tables v2"
54
+ link: "/components/table-v2"
53
55
  - label: "Timeline"
54
56
  link: "/components/timeline"
55
57
  - label: "Navbar"
@@ -86,5 +86,93 @@
86
86
  "district": "3rd"
87
87
  },
88
88
  "website": "https://susielee.house.gov"
89
+ },
90
+ {
91
+ "name": "Ashley Hinson",
92
+ "email": "ashley.hinson@mail.house.gov",
93
+ "party": "republican",
94
+ "region": {
95
+ "state": "Iowa",
96
+ "abbr": "IA",
97
+ "district": "2nd"
98
+ },
99
+ "website": "https://hinson.house.gov"
100
+ },
101
+ {
102
+ "name": "Ilhan Omar",
103
+ "email": "ilhan.omar@mail.house.gov",
104
+ "party": "democrat",
105
+ "region": {
106
+ "state": "Minnesota",
107
+ "abbr": "MN",
108
+ "district": "5th"
109
+ },
110
+ "website": "https://omar.house.gov"
111
+ },
112
+ {
113
+ "name": "Nancy Mace",
114
+ "email": "nancy.mace@mail.house.gov",
115
+ "party": "republican",
116
+ "region": {
117
+ "state": "South Carolina",
118
+ "abbr": "SC",
119
+ "district": "1st"
120
+ },
121
+ "website": "https://mace.house.gov"
122
+ },
123
+ {
124
+ "name": "Maxwell Frost",
125
+ "email": "maxwell.frost@mail.house.gov",
126
+ "party": "democrat",
127
+ "region": {
128
+ "state": "Florida",
129
+ "abbr": "FL",
130
+ "district": "10th"
131
+ },
132
+ "website": "https://frost.house.gov"
133
+ },
134
+ {
135
+ "name": "Dan Crenshaw",
136
+ "email": "dan.crenshaw@mail.house.gov",
137
+ "party": "republican",
138
+ "region": {
139
+ "state": "Texas",
140
+ "abbr": "TX",
141
+ "district": "2nd"
142
+ },
143
+ "website": "https://crenshaw.house.gov"
144
+ },
145
+ {
146
+ "name": "Ayanna Pressley",
147
+ "email": "ayanna.pressley@mail.house.gov",
148
+ "party": "democrat",
149
+ "region": {
150
+ "state": "Massachusetts",
151
+ "abbr": "MA",
152
+ "district": "7th"
153
+ },
154
+ "website": "https://pressley.house.gov"
155
+ },
156
+ {
157
+ "name": "Lauren Boebert",
158
+ "email": "lauren.boebert@mail.house.gov",
159
+ "party": "republican",
160
+ "region": {
161
+ "state": "Colorado",
162
+ "abbr": "CO",
163
+ "district": "3rd"
164
+ },
165
+ "website": "https://boebert.house.gov"
166
+ },
167
+ {
168
+ "name": "Ruben Gallego",
169
+ "email": "ruben.gallego@mail.house.gov",
170
+ "party": "democrat",
171
+ "region": {
172
+ "state": "Arizona",
173
+ "abbr": "AZ",
174
+ "district": "3rd"
175
+ },
176
+ "website": "https://gallego.house.gov"
89
177
  }
90
178
  ]
@@ -0,0 +1,367 @@
1
+ ---
2
+ title: Tables v2
3
+ ---
4
+
5
+ import { Excerpt } from "../../components/Excerpt.jsx";
6
+ import { TableV2 } from "../../components/LoadableTabler.jsx";
7
+ import congressPeople from "../../data/congressPeople.json";
8
+
9
+ Tables v2 is a new data table built on TanStack Table v8 with Tabler styling. It is fully controlled: you pass the current page of `data`, the total row count, and the active `sorting`. This design fits server-side pagination and ordering out of the box.
10
+
11
+ > Note: live previews on this page may require a package version that exports `TableV2`. If you don’t see live examples, the code snippets still illustrate usage.
12
+
13
+ ## Signature
14
+
15
+ ```jsx
16
+ import { TableV2 } from "tabler-react-2";
17
+
18
+ <TableV2 {...props} />;
19
+ ```
20
+
21
+ ### Core props
22
+
23
+ | Prop | Required | Type | Description |
24
+ | ---------------------- | -------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
25
+ | `columns` | Yes | TanStack `ColumnDef[]` | Column definitions using `accessorKey`, `header`, and optional `cell` renderers. |
26
+ | `data` | Yes | `any[]` | Current page rows only. |
27
+ | `totalRows` | Yes | `number` | Total available row count (for page count and range text). |
28
+ | `page` | Yes | `number` | Current page (1-based). |
29
+ | `size` | Yes | `number` | Current page size. |
30
+ | `onPageChange` | Yes | `(page:number) => void` | Called with next 1-based page. |
31
+ | `onSizeChange` | Yes | `(size:number) => void` | Called with next page size. Typically also reset page to 1. |
32
+ | `sorting` | Yes | `{ id:string, desc?:boolean }[]` | Active ordering (TanStack format). Empty array for “no sort”. |
33
+ | `onSortingChange` | Yes | `(next) => void` | Called when the user clicks a sortable header. |
34
+ | `loading` | No | `boolean` | Shows a small spinner and disables pager while loading. |
35
+ | `headerSticky` | No | `boolean` | Makes the header stick to the top of the card. |
36
+ | `emptyState` | No | `string \| () => ReactNode` | What to render when there are no rows. |
37
+ | `getRowId` | No | `(row) => string` | Supply when your rows need a stable custom ID. |
38
+ | `rowSelection` | No | `Record<string, boolean>` | Controlled selection map. |
39
+ | `onRowSelectionChange` | No | `(updater) => void` | Controlled selection callback. |
40
+ | `renderToolbarLeft` | No | `({ page,size,totalRows,sorting }) => ReactNode` | Custom left content in the card header. |
41
+ | `renderToolbarRight` | No | same | Custom right content in the card header. |
42
+
43
+ ## Basic usage (client or server data)
44
+
45
+ The table is controlled; keep `page`, `size`, and `sorting` in your component state. The example below uses in-memory data, but the same shape works with server APIs.
46
+
47
+ ```jsx
48
+ import { useMemo, useState } from "react";
49
+ import { TableV2 } from "tabler-react-2";
50
+
51
+ const allRows = congressPeople; // e.g., from your API
52
+
53
+ export default function Demo() {
54
+ const [page, setPage] = useState(1);
55
+ const [size, setSize] = useState(5);
56
+ const [sorting, setSorting] = useState([]); // [{ id:'name', desc:false }]
57
+
58
+ const columns = useMemo(
59
+ () => [
60
+ { accessorKey: "name", header: "Name" },
61
+ { accessorKey: "party", header: "Party" },
62
+ { accessorKey: "region.state", header: "State" },
63
+ {
64
+ accessorKey: "email",
65
+ header: "Email",
66
+ cell: ({ getValue }) => (
67
+ <a className="text-reset" href={`mailto:${getValue()}`}>
68
+ {getValue()}
69
+ </a>
70
+ ),
71
+ },
72
+ ],
73
+ []
74
+ );
75
+
76
+ // client-side slice just for the demo
77
+ const ordered = useMemo(() => {
78
+ if (!sorting.length) return allRows;
79
+ const { id, desc } = sorting[0];
80
+ const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
81
+ const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
82
+ const sorted = [...allRows].sort((a, b) => cmp(val(a), val(b)));
83
+ return desc ? sorted.reverse() : sorted;
84
+ }, [sorting]);
85
+
86
+ const start = (page - 1) * size;
87
+ const pageData = ordered.slice(start, start + size);
88
+
89
+ return (
90
+ <TableV2
91
+ columns={columns}
92
+ data={pageData}
93
+ totalRows={allRows.length}
94
+ page={page}
95
+ size={size}
96
+ onPageChange={setPage}
97
+ onSizeChange={(n) => {
98
+ setPage(1);
99
+ setSize(n);
100
+ }}
101
+ sorting={sorting}
102
+ onSortingChange={(next) => {
103
+ setPage(1);
104
+ setSorting(next);
105
+ }}
106
+ />
107
+ );
108
+ }
109
+ ```
110
+
111
+ <Excerpt>
112
+ {(() => {
113
+ const React = require("react");
114
+ const { useMemo, useState } = React;
115
+
116
+ // Local, fully functional demo state (client-side slice)
117
+ const allRows = congressPeople;
118
+ const [page, setPage] = useState(1);
119
+ const [size, setSize] = useState(10);
120
+ const [sorting, setSorting] = useState([]); // [{ id:'name', desc:false }]
121
+
122
+ const ordered = useMemo(() => {
123
+ if (!sorting.length) return allRows;
124
+ const { id, desc } = sorting[0];
125
+ const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
126
+ const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
127
+ const sorted = [...allRows].sort((a, b) => cmp(val(a), val(b)));
128
+ return desc ? sorted.reverse() : sorted;
129
+ }, [sorting]);
130
+
131
+ const start = (page - 1) * size;
132
+ const pageData = ordered.slice(start, start + size);
133
+
134
+ return (
135
+ <TableV2
136
+ columns={[
137
+ { accessorKey: "name", header: "Name" },
138
+ { accessorKey: "party", header: "Party" },
139
+ { accessorKey: "region.state", header: "State" },
140
+ {
141
+ accessorKey: "email",
142
+ header: "Email",
143
+ cell: ({ getValue }) => (
144
+ <a className="text-reset" href={`mailto:${getValue()}`}>
145
+ {getValue()}
146
+ </a>
147
+ ),
148
+ },
149
+ ]}
150
+ data={pageData}
151
+ totalRows={allRows.length}
152
+ page={page}
153
+ size={size}
154
+ onPageChange={setPage}
155
+ onSizeChange={(n) => {
156
+ setPage(1);
157
+ setSize(n);
158
+ }}
159
+ sorting={sorting}
160
+ onSortingChange={(next) => {
161
+ setPage(1);
162
+ setSorting(next);
163
+ }}
164
+ loading={false}
165
+ />
166
+ );
167
+ })()}
168
+
169
+ </Excerpt>
170
+
171
+ ## Server-side workflow
172
+
173
+ When fetching from an API, keep the same controlled state, but fetch rows for the current `page`, `size`, and `sorting`.
174
+
175
+ ```jsx
176
+ const [page, setPage] = useState(1);
177
+ const [size, setSize] = useState(25);
178
+ const [sorting, setSorting] = useState([]);
179
+ const [rows, setRows] = useState([]);
180
+ const [total, setTotal] = useState(0);
181
+ const [loading, setLoading] = useState(false);
182
+
183
+ useEffect(() => {
184
+ let isMounted = true;
185
+ (async () => {
186
+ setLoading(true);
187
+ try {
188
+ const res = await fetchPeople({ page, size, sorting });
189
+ if (!isMounted) return;
190
+ setRows(res.items);
191
+ setTotal(res.total);
192
+ } finally {
193
+ setLoading(false);
194
+ }
195
+ })();
196
+ return () => {
197
+ isMounted = false;
198
+ };
199
+ }, [page, size, sorting]);
200
+
201
+ <TableV2
202
+ columns={columns}
203
+ data={rows}
204
+ totalRows={total}
205
+ page={page}
206
+ size={size}
207
+ onPageChange={setPage}
208
+ onSizeChange={(n) => {
209
+ setPage(1);
210
+ setSize(n);
211
+ }}
212
+ sorting={sorting}
213
+ onSortingChange={(next) => {
214
+ setPage(1);
215
+ setSorting(next);
216
+ }}
217
+ loading={loading}
218
+ />;
219
+ ```
220
+
221
+ ### Live async demo (simulated API)
222
+
223
+ The preview below simulates a server by sorting and slicing in a faux `fetch` with a 1s delay.
224
+
225
+ <Excerpt>
226
+ {(() => {
227
+ const React = require("react");
228
+ const { useEffect, useMemo, useState } = React;
229
+
230
+ // Simulated API that delays 1s and then returns sorted, paged rows
231
+ const fetchPeople = ({ page, size, sorting }) =>
232
+ new Promise((resolve) => {
233
+ setTimeout(() => {
234
+ const all = congressPeople;
235
+ const total = all.length;
236
+
237
+ // server-side order
238
+ let ordered = all;
239
+ if (sorting?.length) {
240
+ const { id, desc } = sorting[0];
241
+ const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
242
+ const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
243
+ const sorted = [...all].sort((a, b) => cmp(val(a), val(b)));
244
+ ordered = desc ? sorted.reverse() : sorted;
245
+ }
246
+
247
+ // server-side page
248
+ const start = (page - 1) * size;
249
+ const items = ordered.slice(start, start + size);
250
+ resolve({ items, total });
251
+ }, 1000);
252
+ });
253
+
254
+ const columns = useMemo(
255
+ () => [
256
+ { accessorKey: "name", header: "Name" },
257
+ { accessorKey: "party", header: "Party" },
258
+ { accessorKey: "region.state", header: "State" },
259
+ {
260
+ accessorKey: "email",
261
+ header: "Email",
262
+ cell: ({ getValue }) => (
263
+ <a className="text-reset" href={`mailto:${getValue()}`}>
264
+ {getValue()}
265
+ </a>
266
+ ),
267
+ },
268
+ ],
269
+ []
270
+ );
271
+
272
+ const [page, setPage] = useState(1);
273
+ const [size, setSize] = useState(10);
274
+ const [sorting, setSorting] = useState([]);
275
+ const [rows, setRows] = useState([]);
276
+ const [total, setTotal] = useState(0);
277
+ const [loading, setLoading] = useState(false);
278
+
279
+ useEffect(() => {
280
+ let isMounted = true;
281
+ setLoading(true);
282
+ fetchPeople({ page, size, sorting }).then((res) => {
283
+ if (!isMounted) return;
284
+ setRows(res.items);
285
+ setTotal(res.total);
286
+ setLoading(false);
287
+ });
288
+ return () => {
289
+ isMounted = false;
290
+ };
291
+ }, [page, size, sorting]);
292
+
293
+ return (
294
+ <TableV2
295
+ columns={columns}
296
+ data={rows}
297
+ totalRows={total}
298
+ page={page}
299
+ size={size}
300
+ onPageChange={setPage}
301
+ onSizeChange={(n) => {
302
+ setPage(1);
303
+ setSize(n);
304
+ }}
305
+ sorting={sorting}
306
+ onSortingChange={(next) => {
307
+ setPage(1);
308
+ setSorting(next);
309
+ }}
310
+ loading={loading}
311
+ />
312
+ );
313
+ })()}
314
+
315
+ </Excerpt>
316
+
317
+ ## Extras
318
+
319
+ - Sticky header: pass `headerSticky` to keep headers visible while scrolling.
320
+ - Toolbars: use `renderToolbarLeft`/`renderToolbarRight` to add filters or actions.
321
+ - Row selection: control with `rowSelection` and `onRowSelectionChange`.
322
+
323
+ ### Sticky header example
324
+
325
+ ```jsx
326
+ <TableV2 {...commonProps} headerSticky />
327
+ ```
328
+
329
+ ### Toolbar example
330
+
331
+ ```jsx
332
+ <TableV2
333
+ {...commonProps}
334
+ renderToolbarLeft={() => (
335
+ <input className="form-control form-control-sm" placeholder="Search" />
336
+ )}
337
+ renderToolbarRight={() => (
338
+ <button className="btn btn-sm btn-primary">Add</button>
339
+ )}
340
+ />
341
+ ```
342
+
343
+ ### Row selection (minimal)
344
+
345
+ ```jsx
346
+ const [rowSelection, setRowSelection] = useState({});
347
+
348
+ <TableV2
349
+ {...commonProps}
350
+ rowSelection={rowSelection}
351
+ onRowSelectionChange={setRowSelection}
352
+ columns={[
353
+ {
354
+ id: "select",
355
+ header: () => null,
356
+ cell: ({ row }) => (
357
+ <input
358
+ type="checkbox"
359
+ checked={row.getIsSelected()}
360
+ onChange={row.getToggleSelectedHandler()}
361
+ />
362
+ ),
363
+ },
364
+ ...columns,
365
+ ]}
366
+ />;
367
+ ```
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "tabler-react-2",
3
- "version": "0.1.153-alpha.2",
3
+ "version": "0.1.154",
4
4
  "description": "A react implementation of Tabler ui",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "test": "cd demo && yarn start",
8
8
  "build:css": "postcss src/**/*.css --dir dist",
9
- "build": "babel src --out-dir dist && npm run build:css"
9
+ "build": "babel src --out-dir dist && npm run build:css",
10
+ "docs:vanilla": "yarn --cwd docs start",
11
+ "docs": "cpulimit -l 20 -- yarn --cwd docs start"
10
12
  },
11
13
  "author": "Jack Crane",
12
14
  "license": "MIT",
@@ -15,6 +17,8 @@
15
17
  "@babel/core": "^7.24.8",
16
18
  "@babel/preset-env": "^7.24.8",
17
19
  "@babel/preset-react": "^7.24.7",
20
+ "@emotion/react": "^11.14.0",
21
+ "@tanstack/react-table": "^8.21.3",
18
22
  "classnames": "^2.5.1",
19
23
  "prop-types": "^15.8.1",
20
24
  "react": "^18.3.1",