@rufous/ui 0.2.106 → 0.3.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.
package/dist/main.cjs CHANGED
@@ -4439,6 +4439,12 @@ function DataGrid({
4439
4439
  columns: initialColumnsProp,
4440
4440
  data,
4441
4441
  actions,
4442
+ loading = false,
4443
+ pagination = true,
4444
+ paginationMode = "client",
4445
+ rowCount,
4446
+ paginationModel,
4447
+ onPaginationModelChange,
4442
4448
  pageSize: initialPageSize = 10,
4443
4449
  pageSizeOptions = [5, 10, 25, 50],
4444
4450
  title,
@@ -4479,6 +4485,23 @@ function DataGrid({
4479
4485
  const [sortDirection, setSortDirection] = (0, import_react23.useState)(null);
4480
4486
  const [filterText, setFilterText] = (0, import_react23.useState)("");
4481
4487
  const [currentPage, setCurrentPage] = (0, import_react23.useState)(1);
4488
+ const activePage = paginationModel ? paginationModel.page + 1 : currentPage;
4489
+ const activePageSize = paginationModel ? paginationModel.pageSize : pageSize;
4490
+ const handlePageChange = (newPage) => {
4491
+ if (onPaginationModelChange) {
4492
+ onPaginationModelChange({ page: newPage - 1, pageSize: activePageSize });
4493
+ } else {
4494
+ setCurrentPage(newPage);
4495
+ }
4496
+ };
4497
+ const handlePageSizeChange = (newSize) => {
4498
+ if (onPaginationModelChange) {
4499
+ onPaginationModelChange({ page: 0, pageSize: newSize });
4500
+ } else {
4501
+ setPageSize(newSize);
4502
+ setCurrentPage(1);
4503
+ }
4504
+ };
4482
4505
  const [resizingColumn, setResizingColumn] = (0, import_react23.useState)(null);
4483
4506
  const [startX, setStartX] = (0, import_react23.useState)(0);
4484
4507
  const [startWidth, setStartWidth] = (0, import_react23.useState)(0);
@@ -4711,11 +4734,14 @@ function DataGrid({
4711
4734
  return 0;
4712
4735
  });
4713
4736
  }, [filteredData, sortField, sortDirection, resolvedColumns]);
4714
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
4737
+ const isServer = paginationMode === "server";
4738
+ const totalRows = isServer ? rowCount ?? data.length : filteredData.length;
4739
+ const totalPages = Math.max(1, Math.ceil(totalRows / activePageSize));
4715
4740
  const paginatedData = (0, import_react23.useMemo)(() => {
4716
- const start = (currentPage - 1) * pageSize;
4717
- return sortedData.slice(start, start + pageSize);
4718
- }, [sortedData, currentPage, pageSize]);
4741
+ if (isServer) return data;
4742
+ const start = (activePage - 1) * activePageSize;
4743
+ return sortedData.slice(start, start + activePageSize);
4744
+ }, [isServer, data, sortedData, activePage, activePageSize]);
4719
4745
  const handleExport = () => {
4720
4746
  const exportableCols = resolvedColumns.filter((c) => !c.hidden && c.isExportable !== false);
4721
4747
  const headers = exportableCols.map((c) => c.headerName).join(",");
@@ -4810,7 +4836,7 @@ function DataGrid({
4810
4836
  onClick: () => setShowManageColumns(true)
4811
4837
  },
4812
4838
  /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Columns, { size: 16 })
4813
- )), /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 ? " dg-table-wrap--empty" : ""}` }, /* @__PURE__ */ import_react23.default.createElement("table", { className: "dg-table" }, /* @__PURE__ */ import_react23.default.createElement("thead", null, /* @__PURE__ */ import_react23.default.createElement("tr", null, visibleColumns.map((col, idx) => {
4839
+ )), /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ import_react23.default.createElement("table", { className: "dg-table" }, /* @__PURE__ */ import_react23.default.createElement("thead", null, /* @__PURE__ */ import_react23.default.createElement("tr", null, visibleColumns.map((col, idx) => {
4814
4840
  const colField = String(col.field);
4815
4841
  const width = columnWidths[colField] || 200;
4816
4842
  const leftOffset = getLeftOffset(col, idx);
@@ -4924,17 +4950,14 @@ function DataGrid({
4924
4950
  },
4925
4951
  action.icon
4926
4952
  )))));
4927
- })()))))), paginatedData.length === 0 && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ import_react23.default.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react23.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react23.default.createElement(
4953
+ })()))))), paginatedData.length === 0 && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ import_react23.default.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react23.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react23.default.createElement(
4928
4954
  "select",
4929
4955
  {
4930
- value: pageSize,
4931
- onChange: (e) => {
4932
- setPageSize(Number(e.target.value));
4933
- setCurrentPage(1);
4934
- }
4956
+ value: activePageSize,
4957
+ onChange: (e) => handlePageSizeChange(Number(e.target.value))
4935
4958
  },
4936
4959
  pageSizeOptions.map((o) => /* @__PURE__ */ import_react23.default.createElement("option", { key: o, value: o }, o))
4937
- )), /* @__PURE__ */ import_react23.default.createElement("span", null, (currentPage - 1) * pageSize + 1, "\u2013", Math.min(currentPage * pageSize, filteredData.length), " of ", filteredData.length)), /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-nav" }, /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-page-btn", disabled: currentPage === 1, onClick: () => setCurrentPage((p) => p - 1) }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronLeft, { size: 15 })), /* @__PURE__ */ import_react23.default.createElement("span", { className: "dg-page-fraction" }, currentPage, " / ", totalPages), /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-page-btn", disabled: currentPage === totalPages, onClick: () => setCurrentPage((p) => p + 1) }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronRight, { size: 15 })))), activeMenu && /* @__PURE__ */ import_react23.default.createElement(
4960
+ )), /* @__PURE__ */ import_react23.default.createElement("span", null, (activePage - 1) * activePageSize + 1, "\u2013", Math.min(activePage * activePageSize, totalRows), " of ", totalRows)), /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-nav" }, /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-page-btn", disabled: activePage === 1, onClick: () => handlePageChange(activePage - 1) }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronLeft, { size: 15 })), /* @__PURE__ */ import_react23.default.createElement("span", { className: "dg-page-fraction" }, activePage, " / ", totalPages), /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-page-btn", disabled: activePage === totalPages, onClick: () => handlePageChange(activePage + 1) }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronRight, { size: 15 })))), activeMenu && /* @__PURE__ */ import_react23.default.createElement(
4938
4961
  "div",
4939
4962
  {
4940
4963
  ref: menuRef,
package/dist/main.css CHANGED
@@ -443,6 +443,7 @@
443
443
  overflow-x: auto;
444
444
  overflow-y: auto;
445
445
  flex: 1;
446
+ position: relative;
446
447
  }
447
448
  .dg-table-wrap--empty {
448
449
  display: flex;
@@ -936,6 +937,30 @@
936
937
  --tf-hover-border-color: var(--text-secondary);
937
938
  --tf-primary-color: var(--primary-color);
938
939
  }
940
+ .dg-loading-overlay {
941
+ position: absolute;
942
+ inset: 0;
943
+ top: 41px;
944
+ background: rgba(255, 255, 255, 0.65);
945
+ display: flex;
946
+ align-items: center;
947
+ justify-content: center;
948
+ z-index: 20;
949
+ pointer-events: all;
950
+ }
951
+ .dg-loading-spinner {
952
+ width: 32px;
953
+ height: 32px;
954
+ border: 3px solid rgba(0, 0, 0, 0.1);
955
+ border-top-color: var(--primary-color, #f15b24);
956
+ border-radius: 50%;
957
+ animation: dg-spin 0.7s linear infinite;
958
+ }
959
+ @keyframes dg-spin {
960
+ to {
961
+ transform: rotate(360deg);
962
+ }
963
+ }
939
964
  .dg-empty-state {
940
965
  flex: 1;
941
966
  display: flex;
@@ -6321,11 +6346,11 @@ pre {
6321
6346
  z-index: 2;
6322
6347
  }
6323
6348
  .rf-text-field__adornment--start {
6324
- margin-left: 14px;
6349
+ margin-left: 0px;
6325
6350
  margin-right: -6px;
6326
6351
  }
6327
6352
  .rf-text-field__adornment--end {
6328
- margin-right: 14px;
6353
+ margin-right: 0px;
6329
6354
  margin-left: -6px;
6330
6355
  }
6331
6356
  .rf-text-field--standard .rf-text-field__adornment--start {
package/dist/main.d.cts CHANGED
@@ -863,10 +863,37 @@ interface DataGridToolbarSlot {
863
863
  content: React__default.ReactNode;
864
864
  align?: 'left' | 'center' | 'right';
865
865
  }
866
+ interface PaginationModel {
867
+ /** 0-indexed current page */
868
+ page: number;
869
+ pageSize: number;
870
+ }
866
871
  interface DataGridProps<T> {
867
872
  columns: Column<T>[];
868
873
  data: T[];
869
874
  actions?: Action<T>[] | ((item: T) => Action<T>[]);
875
+ /** Show a loading overlay over the table body */
876
+ loading?: boolean;
877
+ /** Show the pagination bar. Defaults to true. */
878
+ pagination?: boolean;
879
+ /**
880
+ * 'client' (default) — DataGrid filters, sorts, and paginates data internally.
881
+ * 'server' — data is already the current page; DataGrid skips client-side
882
+ * filtering/sorting/slicing and delegates everything to the server.
883
+ */
884
+ paginationMode?: 'client' | 'server';
885
+ /**
886
+ * Total number of rows across all pages (server mode).
887
+ * Used to compute total page count when paginationMode="server".
888
+ */
889
+ rowCount?: number;
890
+ /**
891
+ * Controlled pagination state. page is 0-indexed.
892
+ * When provided, the DataGrid uses these values instead of internal state.
893
+ */
894
+ paginationModel?: PaginationModel;
895
+ /** Called whenever the page or pageSize changes. page is 0-indexed. */
896
+ onPaginationModelChange?: (model: PaginationModel) => void;
870
897
  pageSize?: number;
871
898
  pageSizeOptions?: number[];
872
899
  title?: string;
@@ -886,7 +913,7 @@ interface DataGridProps<T> {
886
913
 
887
914
  declare function DataGrid<T extends {
888
915
  id: string | number;
889
- }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
916
+ }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
890
917
 
891
918
  type SelectOption = {
892
919
  value: string | number;
package/dist/main.d.ts CHANGED
@@ -863,10 +863,37 @@ interface DataGridToolbarSlot {
863
863
  content: React__default.ReactNode;
864
864
  align?: 'left' | 'center' | 'right';
865
865
  }
866
+ interface PaginationModel {
867
+ /** 0-indexed current page */
868
+ page: number;
869
+ pageSize: number;
870
+ }
866
871
  interface DataGridProps<T> {
867
872
  columns: Column<T>[];
868
873
  data: T[];
869
874
  actions?: Action<T>[] | ((item: T) => Action<T>[]);
875
+ /** Show a loading overlay over the table body */
876
+ loading?: boolean;
877
+ /** Show the pagination bar. Defaults to true. */
878
+ pagination?: boolean;
879
+ /**
880
+ * 'client' (default) — DataGrid filters, sorts, and paginates data internally.
881
+ * 'server' — data is already the current page; DataGrid skips client-side
882
+ * filtering/sorting/slicing and delegates everything to the server.
883
+ */
884
+ paginationMode?: 'client' | 'server';
885
+ /**
886
+ * Total number of rows across all pages (server mode).
887
+ * Used to compute total page count when paginationMode="server".
888
+ */
889
+ rowCount?: number;
890
+ /**
891
+ * Controlled pagination state. page is 0-indexed.
892
+ * When provided, the DataGrid uses these values instead of internal state.
893
+ */
894
+ paginationModel?: PaginationModel;
895
+ /** Called whenever the page or pageSize changes. page is 0-indexed. */
896
+ onPaginationModelChange?: (model: PaginationModel) => void;
870
897
  pageSize?: number;
871
898
  pageSizeOptions?: number[];
872
899
  title?: string;
@@ -886,7 +913,7 @@ interface DataGridProps<T> {
886
913
 
887
914
  declare function DataGrid<T extends {
888
915
  id: string | number;
889
- }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
916
+ }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
890
917
 
891
918
  type SelectOption = {
892
919
  value: string | number;
package/dist/main.js CHANGED
@@ -4311,6 +4311,12 @@ function DataGrid({
4311
4311
  columns: initialColumnsProp,
4312
4312
  data,
4313
4313
  actions,
4314
+ loading = false,
4315
+ pagination = true,
4316
+ paginationMode = "client",
4317
+ rowCount,
4318
+ paginationModel,
4319
+ onPaginationModelChange,
4314
4320
  pageSize: initialPageSize = 10,
4315
4321
  pageSizeOptions = [5, 10, 25, 50],
4316
4322
  title,
@@ -4351,6 +4357,23 @@ function DataGrid({
4351
4357
  const [sortDirection, setSortDirection] = useState9(null);
4352
4358
  const [filterText, setFilterText] = useState9("");
4353
4359
  const [currentPage, setCurrentPage] = useState9(1);
4360
+ const activePage = paginationModel ? paginationModel.page + 1 : currentPage;
4361
+ const activePageSize = paginationModel ? paginationModel.pageSize : pageSize;
4362
+ const handlePageChange = (newPage) => {
4363
+ if (onPaginationModelChange) {
4364
+ onPaginationModelChange({ page: newPage - 1, pageSize: activePageSize });
4365
+ } else {
4366
+ setCurrentPage(newPage);
4367
+ }
4368
+ };
4369
+ const handlePageSizeChange = (newSize) => {
4370
+ if (onPaginationModelChange) {
4371
+ onPaginationModelChange({ page: 0, pageSize: newSize });
4372
+ } else {
4373
+ setPageSize(newSize);
4374
+ setCurrentPage(1);
4375
+ }
4376
+ };
4354
4377
  const [resizingColumn, setResizingColumn] = useState9(null);
4355
4378
  const [startX, setStartX] = useState9(0);
4356
4379
  const [startWidth, setStartWidth] = useState9(0);
@@ -4583,11 +4606,14 @@ function DataGrid({
4583
4606
  return 0;
4584
4607
  });
4585
4608
  }, [filteredData, sortField, sortDirection, resolvedColumns]);
4586
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
4609
+ const isServer = paginationMode === "server";
4610
+ const totalRows = isServer ? rowCount ?? data.length : filteredData.length;
4611
+ const totalPages = Math.max(1, Math.ceil(totalRows / activePageSize));
4587
4612
  const paginatedData = useMemo2(() => {
4588
- const start = (currentPage - 1) * pageSize;
4589
- return sortedData.slice(start, start + pageSize);
4590
- }, [sortedData, currentPage, pageSize]);
4613
+ if (isServer) return data;
4614
+ const start = (activePage - 1) * activePageSize;
4615
+ return sortedData.slice(start, start + activePageSize);
4616
+ }, [isServer, data, sortedData, activePage, activePageSize]);
4591
4617
  const handleExport = () => {
4592
4618
  const exportableCols = resolvedColumns.filter((c) => !c.hidden && c.isExportable !== false);
4593
4619
  const headers = exportableCols.map((c) => c.headerName).join(",");
@@ -4682,7 +4708,7 @@ function DataGrid({
4682
4708
  onClick: () => setShowManageColumns(true)
4683
4709
  },
4684
4710
  /* @__PURE__ */ React75.createElement(Columns, { size: 16 })
4685
- )), /* @__PURE__ */ React75.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ React75.createElement(Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ React75.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ React75.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ React75.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 ? " dg-table-wrap--empty" : ""}` }, /* @__PURE__ */ React75.createElement("table", { className: "dg-table" }, /* @__PURE__ */ React75.createElement("thead", null, /* @__PURE__ */ React75.createElement("tr", null, visibleColumns.map((col, idx) => {
4711
+ )), /* @__PURE__ */ React75.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ React75.createElement(Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ React75.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ React75.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ React75.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ React75.createElement("table", { className: "dg-table" }, /* @__PURE__ */ React75.createElement("thead", null, /* @__PURE__ */ React75.createElement("tr", null, visibleColumns.map((col, idx) => {
4686
4712
  const colField = String(col.field);
4687
4713
  const width = columnWidths[colField] || 200;
4688
4714
  const leftOffset = getLeftOffset(col, idx);
@@ -4796,17 +4822,14 @@ function DataGrid({
4796
4822
  },
4797
4823
  action.icon
4798
4824
  )))));
4799
- })()))))), paginatedData.length === 0 && /* @__PURE__ */ React75.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ React75.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React75.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React75.createElement(
4825
+ })()))))), paginatedData.length === 0 && /* @__PURE__ */ React75.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ React75.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React75.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React75.createElement(
4800
4826
  "select",
4801
4827
  {
4802
- value: pageSize,
4803
- onChange: (e) => {
4804
- setPageSize(Number(e.target.value));
4805
- setCurrentPage(1);
4806
- }
4828
+ value: activePageSize,
4829
+ onChange: (e) => handlePageSizeChange(Number(e.target.value))
4807
4830
  },
4808
4831
  pageSizeOptions.map((o) => /* @__PURE__ */ React75.createElement("option", { key: o, value: o }, o))
4809
- )), /* @__PURE__ */ React75.createElement("span", null, (currentPage - 1) * pageSize + 1, "\u2013", Math.min(currentPage * pageSize, filteredData.length), " of ", filteredData.length)), /* @__PURE__ */ React75.createElement("div", { className: "dg-page-nav" }, /* @__PURE__ */ React75.createElement("button", { className: "dg-page-btn", disabled: currentPage === 1, onClick: () => setCurrentPage((p) => p - 1) }, /* @__PURE__ */ React75.createElement(ChevronLeft, { size: 15 })), /* @__PURE__ */ React75.createElement("span", { className: "dg-page-fraction" }, currentPage, " / ", totalPages), /* @__PURE__ */ React75.createElement("button", { className: "dg-page-btn", disabled: currentPage === totalPages, onClick: () => setCurrentPage((p) => p + 1) }, /* @__PURE__ */ React75.createElement(ChevronRight, { size: 15 })))), activeMenu && /* @__PURE__ */ React75.createElement(
4832
+ )), /* @__PURE__ */ React75.createElement("span", null, (activePage - 1) * activePageSize + 1, "\u2013", Math.min(activePage * activePageSize, totalRows), " of ", totalRows)), /* @__PURE__ */ React75.createElement("div", { className: "dg-page-nav" }, /* @__PURE__ */ React75.createElement("button", { className: "dg-page-btn", disabled: activePage === 1, onClick: () => handlePageChange(activePage - 1) }, /* @__PURE__ */ React75.createElement(ChevronLeft, { size: 15 })), /* @__PURE__ */ React75.createElement("span", { className: "dg-page-fraction" }, activePage, " / ", totalPages), /* @__PURE__ */ React75.createElement("button", { className: "dg-page-btn", disabled: activePage === totalPages, onClick: () => handlePageChange(activePage + 1) }, /* @__PURE__ */ React75.createElement(ChevronRight, { size: 15 })))), activeMenu && /* @__PURE__ */ React75.createElement(
4810
4833
  "div",
4811
4834
  {
4812
4835
  ref: menuRef,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.2.106",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",
@@ -93,4 +93,4 @@
93
93
  "react": "^18.0.0 || ^19.0.0",
94
94
  "react-dom": "^18.0.0 || ^19.0.0"
95
95
  }
96
- }
96
+ }