@rufous/ui 0.2.106 → 0.3.1

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
@@ -2252,10 +2252,12 @@ function AutocompleteInner(props, _ref) {
2252
2252
  className = "",
2253
2253
  style,
2254
2254
  sx,
2255
+ open: openProp,
2255
2256
  onOpen,
2256
2257
  onClose
2257
2258
  } = props;
2258
- const [open, setOpen] = (0, import_react19.useState)(false);
2259
+ const [internalOpen, setInternalOpen] = (0, import_react19.useState)(false);
2260
+ const open = openProp !== void 0 ? openProp : internalOpen;
2259
2261
  const [inputStr, setInputStr] = (0, import_react19.useState)("");
2260
2262
  const [filterQuery, setFilterQuery] = (0, import_react19.useState)("");
2261
2263
  const [focusedIdx, setFocusedIdx] = (0, import_react19.useState)(-1);
@@ -2341,20 +2343,20 @@ function AutocompleteInner(props, _ref) {
2341
2343
  const openPopup = (0, import_react19.useCallback)(() => {
2342
2344
  if (disabled) return;
2343
2345
  calcPopupStyle();
2344
- setOpen(true);
2346
+ if (openProp === void 0) setInternalOpen(true);
2345
2347
  setFocusedIdx(-1);
2346
2348
  setFilterQuery("");
2347
2349
  onOpen?.();
2348
- }, [disabled, calcPopupStyle, onOpen]);
2350
+ }, [disabled, calcPopupStyle, onOpen, openProp]);
2349
2351
  const closePopup = (0, import_react19.useCallback)((justSelected = false) => {
2350
- setOpen(false);
2352
+ if (openProp === void 0) setInternalOpen(false);
2351
2353
  setFocusedIdx(-1);
2352
2354
  onClose?.();
2353
2355
  if (!justSelected && !freeSolo && !multiple && value == null) {
2354
2356
  setInputStr("");
2355
2357
  onInputChange?.(null, "");
2356
2358
  }
2357
- }, [freeSolo, multiple, value, onInputChange, onClose]);
2359
+ }, [openProp, freeSolo, multiple, value, onInputChange, onClose]);
2358
2360
  (0, import_react19.useEffect)(() => {
2359
2361
  if (!open) return;
2360
2362
  const handleOutside = (e) => {
@@ -4439,6 +4441,12 @@ function DataGrid({
4439
4441
  columns: initialColumnsProp,
4440
4442
  data,
4441
4443
  actions,
4444
+ loading = false,
4445
+ pagination = true,
4446
+ paginationMode = "client",
4447
+ rowCount,
4448
+ paginationModel,
4449
+ onPaginationModelChange,
4442
4450
  pageSize: initialPageSize = 10,
4443
4451
  pageSizeOptions = [5, 10, 25, 50],
4444
4452
  title,
@@ -4479,6 +4487,23 @@ function DataGrid({
4479
4487
  const [sortDirection, setSortDirection] = (0, import_react23.useState)(null);
4480
4488
  const [filterText, setFilterText] = (0, import_react23.useState)("");
4481
4489
  const [currentPage, setCurrentPage] = (0, import_react23.useState)(1);
4490
+ const activePage = paginationModel ? paginationModel.page + 1 : currentPage;
4491
+ const activePageSize = paginationModel ? paginationModel.pageSize : pageSize;
4492
+ const handlePageChange = (newPage) => {
4493
+ if (onPaginationModelChange) {
4494
+ onPaginationModelChange({ page: newPage - 1, pageSize: activePageSize });
4495
+ } else {
4496
+ setCurrentPage(newPage);
4497
+ }
4498
+ };
4499
+ const handlePageSizeChange = (newSize) => {
4500
+ if (onPaginationModelChange) {
4501
+ onPaginationModelChange({ page: 0, pageSize: newSize });
4502
+ } else {
4503
+ setPageSize(newSize);
4504
+ setCurrentPage(1);
4505
+ }
4506
+ };
4482
4507
  const [resizingColumn, setResizingColumn] = (0, import_react23.useState)(null);
4483
4508
  const [startX, setStartX] = (0, import_react23.useState)(0);
4484
4509
  const [startWidth, setStartWidth] = (0, import_react23.useState)(0);
@@ -4711,11 +4736,14 @@ function DataGrid({
4711
4736
  return 0;
4712
4737
  });
4713
4738
  }, [filteredData, sortField, sortDirection, resolvedColumns]);
4714
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
4739
+ const isServer = paginationMode === "server";
4740
+ const totalRows = isServer ? rowCount ?? data.length : filteredData.length;
4741
+ const totalPages = Math.max(1, Math.ceil(totalRows / activePageSize));
4715
4742
  const paginatedData = (0, import_react23.useMemo)(() => {
4716
- const start = (currentPage - 1) * pageSize;
4717
- return sortedData.slice(start, start + pageSize);
4718
- }, [sortedData, currentPage, pageSize]);
4743
+ if (isServer) return data;
4744
+ const start = (activePage - 1) * activePageSize;
4745
+ return sortedData.slice(start, start + activePageSize);
4746
+ }, [isServer, data, sortedData, activePage, activePageSize]);
4719
4747
  const handleExport = () => {
4720
4748
  const exportableCols = resolvedColumns.filter((c) => !c.hidden && c.isExportable !== false);
4721
4749
  const headers = exportableCols.map((c) => c.headerName).join(",");
@@ -4810,7 +4838,7 @@ function DataGrid({
4810
4838
  onClick: () => setShowManageColumns(true)
4811
4839
  },
4812
4840
  /* @__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) => {
4841
+ )), /* @__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
4842
  const colField = String(col.field);
4815
4843
  const width = columnWidths[colField] || 200;
4816
4844
  const leftOffset = getLeftOffset(col, idx);
@@ -4924,17 +4952,14 @@ function DataGrid({
4924
4952
  },
4925
4953
  action.icon
4926
4954
  )))));
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(
4955
+ })()))))), 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
4956
  "select",
4929
4957
  {
4930
- value: pageSize,
4931
- onChange: (e) => {
4932
- setPageSize(Number(e.target.value));
4933
- setCurrentPage(1);
4934
- }
4958
+ value: activePageSize,
4959
+ onChange: (e) => handlePageSizeChange(Number(e.target.value))
4935
4960
  },
4936
4961
  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(
4962
+ )), /* @__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
4963
  "div",
4939
4964
  {
4940
4965
  ref: menuRef,
@@ -5110,6 +5135,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
5110
5135
  disabled = false,
5111
5136
  required = false,
5112
5137
  multiple = false,
5138
+ groupBy,
5113
5139
  className = "",
5114
5140
  style,
5115
5141
  sx
@@ -5289,32 +5315,51 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
5289
5315
  ),
5290
5316
  helperText && /* @__PURE__ */ import_react24.default.createElement("div", { className: `rf-text-field__helper-text${error ? " rf-text-field__helper-text--error" : ""}` }, helperText),
5291
5317
  open && !disabled && import_react_dom5.default.createPortal(
5292
- /* @__PURE__ */ import_react24.default.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ import_react24.default.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
5293
- const selected = isSelected(opt.value);
5294
- const focused = focusedIdx === idx;
5295
- return /* @__PURE__ */ import_react24.default.createElement(
5296
- "li",
5297
- {
5298
- key: opt.value,
5299
- "data-idx": idx,
5300
- role: "option",
5301
- "aria-selected": selected,
5302
- "aria-disabled": opt.disabled,
5303
- className: [
5304
- "rf-select__option",
5305
- selected ? "rf-select__option--selected" : "",
5306
- focused ? "rf-select__option--focused" : "",
5307
- opt.disabled ? "rf-select__option--disabled" : ""
5308
- ].filter(Boolean).join(" "),
5309
- onMouseEnter: () => setFocusedIdx(idx),
5310
- onMouseLeave: () => setFocusedIdx(-1),
5311
- onMouseDown: (e) => e.preventDefault(),
5312
- onClick: (e) => selectOption(opt, e)
5313
- },
5314
- /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-label" }, opt.label),
5315
- /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ import_react24.default.createElement(CheckIcon2, null))
5316
- );
5317
- }))),
5318
+ /* @__PURE__ */ import_react24.default.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ import_react24.default.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, (() => {
5319
+ const renderOpt = (opt, selectableIdx) => {
5320
+ const selected = isSelected(opt.value);
5321
+ const focused = focusedIdx === selectableIdx;
5322
+ return /* @__PURE__ */ import_react24.default.createElement(
5323
+ "li",
5324
+ {
5325
+ key: opt.value,
5326
+ "data-idx": selectableIdx,
5327
+ role: "option",
5328
+ "aria-selected": selected,
5329
+ "aria-disabled": opt.disabled,
5330
+ className: [
5331
+ "rf-select__option",
5332
+ selected ? "rf-select__option--selected" : "",
5333
+ focused ? "rf-select__option--focused" : "",
5334
+ opt.disabled ? "rf-select__option--disabled" : ""
5335
+ ].filter(Boolean).join(" "),
5336
+ onMouseEnter: () => setFocusedIdx(selectableIdx),
5337
+ onMouseLeave: () => setFocusedIdx(-1),
5338
+ onMouseDown: (e) => e.preventDefault(),
5339
+ onClick: (e) => selectOption(opt, e)
5340
+ },
5341
+ /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-label" }, opt.label),
5342
+ /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ import_react24.default.createElement(CheckIcon2, null))
5343
+ );
5344
+ };
5345
+ let sCounter = 0;
5346
+ const selectableIdxMap = /* @__PURE__ */ new Map();
5347
+ options.forEach((o) => {
5348
+ if (!o.disabled) selectableIdxMap.set(o, sCounter++);
5349
+ });
5350
+ const resolveGroup = groupBy ? groupBy : (o) => o.group ?? "";
5351
+ const hasGroups = groupBy != null || options.some((o) => o.group != null);
5352
+ if (!hasGroups) {
5353
+ return options.map((opt) => renderOpt(opt, selectableIdxMap.get(opt) ?? -1));
5354
+ }
5355
+ const groupMap = /* @__PURE__ */ new Map();
5356
+ options.forEach((opt) => {
5357
+ const g = resolveGroup(opt);
5358
+ if (!groupMap.has(g)) groupMap.set(g, []);
5359
+ groupMap.get(g).push(opt);
5360
+ });
5361
+ return Array.from(groupMap.entries()).map(([groupName, groupOpts]) => /* @__PURE__ */ import_react24.default.createElement("li", { key: groupName, role: "presentation" }, /* @__PURE__ */ import_react24.default.createElement("div", { className: "rf-select__group-header" }, groupName), /* @__PURE__ */ import_react24.default.createElement("ul", { className: "rf-select__group-items", role: "group" }, groupOpts.map((opt) => renderOpt(opt, selectableIdxMap.get(opt) ?? -1)))));
5362
+ })())),
5318
5363
  document.body
5319
5364
  )
5320
5365
  );
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;
@@ -2702,6 +2727,23 @@ pre {
2702
2727
  .rf-select__option--selected .rf-select__option-check {
2703
2728
  opacity: 1;
2704
2729
  }
2730
+ .rf-select__group-header {
2731
+ padding: 6px 16px 4px;
2732
+ font-size: 0.7rem;
2733
+ font-weight: 700;
2734
+ letter-spacing: 0.07em;
2735
+ text-transform: uppercase;
2736
+ color: var(--primary-color, #a41b06);
2737
+ background: #fff5f5;
2738
+ position: sticky;
2739
+ top: 0;
2740
+ z-index: 1;
2741
+ }
2742
+ .rf-select__group-items {
2743
+ padding: 0;
2744
+ list-style: none;
2745
+ margin: 0;
2746
+ }
2705
2747
 
2706
2748
  /* lib/styles/slider.css */
2707
2749
  .rf-slider {
@@ -6321,11 +6363,11 @@ pre {
6321
6363
  z-index: 2;
6322
6364
  }
6323
6365
  .rf-text-field__adornment--start {
6324
- margin-left: 14px;
6366
+ margin-left: 0px;
6325
6367
  margin-right: -6px;
6326
6368
  }
6327
6369
  .rf-text-field__adornment--end {
6328
- margin-right: 14px;
6370
+ margin-right: 0px;
6329
6371
  margin-left: -6px;
6330
6372
  }
6331
6373
  .rf-text-field--standard .rf-text-field__adornment--start {
package/dist/main.d.cts CHANGED
@@ -762,6 +762,15 @@ interface AutocompleteProps<T = string> {
762
762
  style?: CSSProperties;
763
763
  /** Scoped style overrides. Supports nested CSS selectors with & */
764
764
  sx?: SxProp;
765
+ /**
766
+ * Controlled open state. When provided the dropdown respects this value.
767
+ * Pair with `onOpen` and `onClose` to sync your own state:
768
+ * ```
769
+ * const [open, setOpen] = useState(false);
770
+ * <Autocomplete open={open} onOpen={() => setOpen(true)} onClose={() => setOpen(false)} />
771
+ * ```
772
+ */
773
+ open?: boolean;
765
774
  /** Called when the popup opens */
766
775
  onOpen?: () => void;
767
776
  /** Called when the popup closes */
@@ -863,10 +872,37 @@ interface DataGridToolbarSlot {
863
872
  content: React__default.ReactNode;
864
873
  align?: 'left' | 'center' | 'right';
865
874
  }
875
+ interface PaginationModel {
876
+ /** 0-indexed current page */
877
+ page: number;
878
+ pageSize: number;
879
+ }
866
880
  interface DataGridProps<T> {
867
881
  columns: Column<T>[];
868
882
  data: T[];
869
883
  actions?: Action<T>[] | ((item: T) => Action<T>[]);
884
+ /** Show a loading overlay over the table body */
885
+ loading?: boolean;
886
+ /** Show the pagination bar. Defaults to true. */
887
+ pagination?: boolean;
888
+ /**
889
+ * 'client' (default) — DataGrid filters, sorts, and paginates data internally.
890
+ * 'server' — data is already the current page; DataGrid skips client-side
891
+ * filtering/sorting/slicing and delegates everything to the server.
892
+ */
893
+ paginationMode?: 'client' | 'server';
894
+ /**
895
+ * Total number of rows across all pages (server mode).
896
+ * Used to compute total page count when paginationMode="server".
897
+ */
898
+ rowCount?: number;
899
+ /**
900
+ * Controlled pagination state. page is 0-indexed.
901
+ * When provided, the DataGrid uses these values instead of internal state.
902
+ */
903
+ paginationModel?: PaginationModel;
904
+ /** Called whenever the page or pageSize changes. page is 0-indexed. */
905
+ onPaginationModelChange?: (model: PaginationModel) => void;
870
906
  pageSize?: number;
871
907
  pageSizeOptions?: number[];
872
908
  title?: string;
@@ -886,12 +922,13 @@ interface DataGridProps<T> {
886
922
 
887
923
  declare function DataGrid<T extends {
888
924
  id: string | number;
889
- }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
925
+ }>({ 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
926
 
891
927
  type SelectOption = {
892
928
  value: string | number;
893
929
  label: string;
894
930
  disabled?: boolean;
931
+ group?: string;
895
932
  };
896
933
  interface SelectProps {
897
934
  options: SelectOption[] | string[];
@@ -907,6 +944,8 @@ interface SelectProps {
907
944
  disabled?: boolean;
908
945
  required?: boolean;
909
946
  multiple?: boolean;
947
+ /** Group options by returning a category string */
948
+ groupBy?: (option: SelectOption) => string;
910
949
  className?: string;
911
950
  style?: CSSProperties;
912
951
  sx?: SxProp;
package/dist/main.d.ts CHANGED
@@ -762,6 +762,15 @@ interface AutocompleteProps<T = string> {
762
762
  style?: CSSProperties;
763
763
  /** Scoped style overrides. Supports nested CSS selectors with & */
764
764
  sx?: SxProp;
765
+ /**
766
+ * Controlled open state. When provided the dropdown respects this value.
767
+ * Pair with `onOpen` and `onClose` to sync your own state:
768
+ * ```
769
+ * const [open, setOpen] = useState(false);
770
+ * <Autocomplete open={open} onOpen={() => setOpen(true)} onClose={() => setOpen(false)} />
771
+ * ```
772
+ */
773
+ open?: boolean;
765
774
  /** Called when the popup opens */
766
775
  onOpen?: () => void;
767
776
  /** Called when the popup closes */
@@ -863,10 +872,37 @@ interface DataGridToolbarSlot {
863
872
  content: React__default.ReactNode;
864
873
  align?: 'left' | 'center' | 'right';
865
874
  }
875
+ interface PaginationModel {
876
+ /** 0-indexed current page */
877
+ page: number;
878
+ pageSize: number;
879
+ }
866
880
  interface DataGridProps<T> {
867
881
  columns: Column<T>[];
868
882
  data: T[];
869
883
  actions?: Action<T>[] | ((item: T) => Action<T>[]);
884
+ /** Show a loading overlay over the table body */
885
+ loading?: boolean;
886
+ /** Show the pagination bar. Defaults to true. */
887
+ pagination?: boolean;
888
+ /**
889
+ * 'client' (default) — DataGrid filters, sorts, and paginates data internally.
890
+ * 'server' — data is already the current page; DataGrid skips client-side
891
+ * filtering/sorting/slicing and delegates everything to the server.
892
+ */
893
+ paginationMode?: 'client' | 'server';
894
+ /**
895
+ * Total number of rows across all pages (server mode).
896
+ * Used to compute total page count when paginationMode="server".
897
+ */
898
+ rowCount?: number;
899
+ /**
900
+ * Controlled pagination state. page is 0-indexed.
901
+ * When provided, the DataGrid uses these values instead of internal state.
902
+ */
903
+ paginationModel?: PaginationModel;
904
+ /** Called whenever the page or pageSize changes. page is 0-indexed. */
905
+ onPaginationModelChange?: (model: PaginationModel) => void;
870
906
  pageSize?: number;
871
907
  pageSizeOptions?: number[];
872
908
  title?: string;
@@ -886,12 +922,13 @@ interface DataGridProps<T> {
886
922
 
887
923
  declare function DataGrid<T extends {
888
924
  id: string | number;
889
- }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
925
+ }>({ 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
926
 
891
927
  type SelectOption = {
892
928
  value: string | number;
893
929
  label: string;
894
930
  disabled?: boolean;
931
+ group?: string;
895
932
  };
896
933
  interface SelectProps {
897
934
  options: SelectOption[] | string[];
@@ -907,6 +944,8 @@ interface SelectProps {
907
944
  disabled?: boolean;
908
945
  required?: boolean;
909
946
  multiple?: boolean;
947
+ /** Group options by returning a category string */
948
+ groupBy?: (option: SelectOption) => string;
910
949
  className?: string;
911
950
  style?: CSSProperties;
912
951
  sx?: SxProp;
package/dist/main.js CHANGED
@@ -2098,10 +2098,12 @@ function AutocompleteInner(props, _ref) {
2098
2098
  className = "",
2099
2099
  style,
2100
2100
  sx,
2101
+ open: openProp,
2101
2102
  onOpen,
2102
2103
  onClose
2103
2104
  } = props;
2104
- const [open, setOpen] = useState5(false);
2105
+ const [internalOpen, setInternalOpen] = useState5(false);
2106
+ const open = openProp !== void 0 ? openProp : internalOpen;
2105
2107
  const [inputStr, setInputStr] = useState5("");
2106
2108
  const [filterQuery, setFilterQuery] = useState5("");
2107
2109
  const [focusedIdx, setFocusedIdx] = useState5(-1);
@@ -2187,20 +2189,20 @@ function AutocompleteInner(props, _ref) {
2187
2189
  const openPopup = useCallback2(() => {
2188
2190
  if (disabled) return;
2189
2191
  calcPopupStyle();
2190
- setOpen(true);
2192
+ if (openProp === void 0) setInternalOpen(true);
2191
2193
  setFocusedIdx(-1);
2192
2194
  setFilterQuery("");
2193
2195
  onOpen?.();
2194
- }, [disabled, calcPopupStyle, onOpen]);
2196
+ }, [disabled, calcPopupStyle, onOpen, openProp]);
2195
2197
  const closePopup = useCallback2((justSelected = false) => {
2196
- setOpen(false);
2198
+ if (openProp === void 0) setInternalOpen(false);
2197
2199
  setFocusedIdx(-1);
2198
2200
  onClose?.();
2199
2201
  if (!justSelected && !freeSolo && !multiple && value == null) {
2200
2202
  setInputStr("");
2201
2203
  onInputChange?.(null, "");
2202
2204
  }
2203
- }, [freeSolo, multiple, value, onInputChange, onClose]);
2205
+ }, [openProp, freeSolo, multiple, value, onInputChange, onClose]);
2204
2206
  useEffect5(() => {
2205
2207
  if (!open) return;
2206
2208
  const handleOutside = (e) => {
@@ -4311,6 +4313,12 @@ function DataGrid({
4311
4313
  columns: initialColumnsProp,
4312
4314
  data,
4313
4315
  actions,
4316
+ loading = false,
4317
+ pagination = true,
4318
+ paginationMode = "client",
4319
+ rowCount,
4320
+ paginationModel,
4321
+ onPaginationModelChange,
4314
4322
  pageSize: initialPageSize = 10,
4315
4323
  pageSizeOptions = [5, 10, 25, 50],
4316
4324
  title,
@@ -4351,6 +4359,23 @@ function DataGrid({
4351
4359
  const [sortDirection, setSortDirection] = useState9(null);
4352
4360
  const [filterText, setFilterText] = useState9("");
4353
4361
  const [currentPage, setCurrentPage] = useState9(1);
4362
+ const activePage = paginationModel ? paginationModel.page + 1 : currentPage;
4363
+ const activePageSize = paginationModel ? paginationModel.pageSize : pageSize;
4364
+ const handlePageChange = (newPage) => {
4365
+ if (onPaginationModelChange) {
4366
+ onPaginationModelChange({ page: newPage - 1, pageSize: activePageSize });
4367
+ } else {
4368
+ setCurrentPage(newPage);
4369
+ }
4370
+ };
4371
+ const handlePageSizeChange = (newSize) => {
4372
+ if (onPaginationModelChange) {
4373
+ onPaginationModelChange({ page: 0, pageSize: newSize });
4374
+ } else {
4375
+ setPageSize(newSize);
4376
+ setCurrentPage(1);
4377
+ }
4378
+ };
4354
4379
  const [resizingColumn, setResizingColumn] = useState9(null);
4355
4380
  const [startX, setStartX] = useState9(0);
4356
4381
  const [startWidth, setStartWidth] = useState9(0);
@@ -4583,11 +4608,14 @@ function DataGrid({
4583
4608
  return 0;
4584
4609
  });
4585
4610
  }, [filteredData, sortField, sortDirection, resolvedColumns]);
4586
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
4611
+ const isServer = paginationMode === "server";
4612
+ const totalRows = isServer ? rowCount ?? data.length : filteredData.length;
4613
+ const totalPages = Math.max(1, Math.ceil(totalRows / activePageSize));
4587
4614
  const paginatedData = useMemo2(() => {
4588
- const start = (currentPage - 1) * pageSize;
4589
- return sortedData.slice(start, start + pageSize);
4590
- }, [sortedData, currentPage, pageSize]);
4615
+ if (isServer) return data;
4616
+ const start = (activePage - 1) * activePageSize;
4617
+ return sortedData.slice(start, start + activePageSize);
4618
+ }, [isServer, data, sortedData, activePage, activePageSize]);
4591
4619
  const handleExport = () => {
4592
4620
  const exportableCols = resolvedColumns.filter((c) => !c.hidden && c.isExportable !== false);
4593
4621
  const headers = exportableCols.map((c) => c.headerName).join(",");
@@ -4682,7 +4710,7 @@ function DataGrid({
4682
4710
  onClick: () => setShowManageColumns(true)
4683
4711
  },
4684
4712
  /* @__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) => {
4713
+ )), /* @__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
4714
  const colField = String(col.field);
4687
4715
  const width = columnWidths[colField] || 200;
4688
4716
  const leftOffset = getLeftOffset(col, idx);
@@ -4796,17 +4824,14 @@ function DataGrid({
4796
4824
  },
4797
4825
  action.icon
4798
4826
  )))));
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(
4827
+ })()))))), 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
4828
  "select",
4801
4829
  {
4802
- value: pageSize,
4803
- onChange: (e) => {
4804
- setPageSize(Number(e.target.value));
4805
- setCurrentPage(1);
4806
- }
4830
+ value: activePageSize,
4831
+ onChange: (e) => handlePageSizeChange(Number(e.target.value))
4807
4832
  },
4808
4833
  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(
4834
+ )), /* @__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
4835
  "div",
4811
4836
  {
4812
4837
  ref: menuRef,
@@ -4987,6 +5012,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4987
5012
  disabled = false,
4988
5013
  required = false,
4989
5014
  multiple = false,
5015
+ groupBy,
4990
5016
  className = "",
4991
5017
  style,
4992
5018
  sx
@@ -5166,32 +5192,51 @@ var Select = React76.forwardRef(function Select2(props, ref) {
5166
5192
  ),
5167
5193
  helperText && /* @__PURE__ */ React76.createElement("div", { className: `rf-text-field__helper-text${error ? " rf-text-field__helper-text--error" : ""}` }, helperText),
5168
5194
  open && !disabled && ReactDOM4.createPortal(
5169
- /* @__PURE__ */ React76.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ React76.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
5170
- const selected = isSelected(opt.value);
5171
- const focused = focusedIdx === idx;
5172
- return /* @__PURE__ */ React76.createElement(
5173
- "li",
5174
- {
5175
- key: opt.value,
5176
- "data-idx": idx,
5177
- role: "option",
5178
- "aria-selected": selected,
5179
- "aria-disabled": opt.disabled,
5180
- className: [
5181
- "rf-select__option",
5182
- selected ? "rf-select__option--selected" : "",
5183
- focused ? "rf-select__option--focused" : "",
5184
- opt.disabled ? "rf-select__option--disabled" : ""
5185
- ].filter(Boolean).join(" "),
5186
- onMouseEnter: () => setFocusedIdx(idx),
5187
- onMouseLeave: () => setFocusedIdx(-1),
5188
- onMouseDown: (e) => e.preventDefault(),
5189
- onClick: (e) => selectOption(opt, e)
5190
- },
5191
- /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-label" }, opt.label),
5192
- /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ React76.createElement(CheckIcon2, null))
5193
- );
5194
- }))),
5195
+ /* @__PURE__ */ React76.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ React76.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, (() => {
5196
+ const renderOpt = (opt, selectableIdx) => {
5197
+ const selected = isSelected(opt.value);
5198
+ const focused = focusedIdx === selectableIdx;
5199
+ return /* @__PURE__ */ React76.createElement(
5200
+ "li",
5201
+ {
5202
+ key: opt.value,
5203
+ "data-idx": selectableIdx,
5204
+ role: "option",
5205
+ "aria-selected": selected,
5206
+ "aria-disabled": opt.disabled,
5207
+ className: [
5208
+ "rf-select__option",
5209
+ selected ? "rf-select__option--selected" : "",
5210
+ focused ? "rf-select__option--focused" : "",
5211
+ opt.disabled ? "rf-select__option--disabled" : ""
5212
+ ].filter(Boolean).join(" "),
5213
+ onMouseEnter: () => setFocusedIdx(selectableIdx),
5214
+ onMouseLeave: () => setFocusedIdx(-1),
5215
+ onMouseDown: (e) => e.preventDefault(),
5216
+ onClick: (e) => selectOption(opt, e)
5217
+ },
5218
+ /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-label" }, opt.label),
5219
+ /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ React76.createElement(CheckIcon2, null))
5220
+ );
5221
+ };
5222
+ let sCounter = 0;
5223
+ const selectableIdxMap = /* @__PURE__ */ new Map();
5224
+ options.forEach((o) => {
5225
+ if (!o.disabled) selectableIdxMap.set(o, sCounter++);
5226
+ });
5227
+ const resolveGroup = groupBy ? groupBy : (o) => o.group ?? "";
5228
+ const hasGroups = groupBy != null || options.some((o) => o.group != null);
5229
+ if (!hasGroups) {
5230
+ return options.map((opt) => renderOpt(opt, selectableIdxMap.get(opt) ?? -1));
5231
+ }
5232
+ const groupMap = /* @__PURE__ */ new Map();
5233
+ options.forEach((opt) => {
5234
+ const g = resolveGroup(opt);
5235
+ if (!groupMap.has(g)) groupMap.set(g, []);
5236
+ groupMap.get(g).push(opt);
5237
+ });
5238
+ return Array.from(groupMap.entries()).map(([groupName, groupOpts]) => /* @__PURE__ */ React76.createElement("li", { key: groupName, role: "presentation" }, /* @__PURE__ */ React76.createElement("div", { className: "rf-select__group-header" }, groupName), /* @__PURE__ */ React76.createElement("ul", { className: "rf-select__group-items", role: "group" }, groupOpts.map((opt) => renderOpt(opt, selectableIdxMap.get(opt) ?? -1)))));
5239
+ })())),
5195
5240
  document.body
5196
5241
  )
5197
5242
  );
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.01",
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
+ }