@juv/codego-react-ui 3.4.8 → 3.5.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/index.cjs CHANGED
@@ -6480,7 +6480,7 @@ var import_react_dom2 = require("react-dom");
6480
6480
  var import_axios3 = __toESM(require("axios"), 1);
6481
6481
  var import_lucide_react17 = require("lucide-react");
6482
6482
  var import_jsx_runtime32 = require("react/jsx-runtime");
6483
- function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce = 300, transform, manual = false, refresh: refreshEnabled = false, refreshInterval = 0, hardReload, onSuccess, onError }) {
6483
+ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce = 300, transform, manual = false, refresh: refreshEnabled = false, refreshInterval = 0, hardReload, onSuccess, onError, filter: filterFields, sort: sortKeys }) {
6484
6484
  const [data, setData] = React28.useState([]);
6485
6485
  const [columns, setColumns] = React28.useState([]);
6486
6486
  const [currentPage, setCurrentPage] = React28.useState(1);
@@ -6490,6 +6490,15 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6490
6490
  const [tick, setTick] = React28.useState(0);
6491
6491
  const [searchValue, setSearchValue] = React28.useState("");
6492
6492
  const debounceTimer = React28.useRef(void 0);
6493
+ const [filterValues, setFilterValues] = React28.useState(() => {
6494
+ const init = {};
6495
+ filterFields?.forEach((f) => {
6496
+ init[f.key] = f.type === "checkbox" || f.type === "toggle" ? false : "";
6497
+ });
6498
+ return init;
6499
+ });
6500
+ const [sortKey, setSortKey] = React28.useState("");
6501
+ const [sortDir, setSortDir] = React28.useState("asc");
6493
6502
  React28.useEffect(() => {
6494
6503
  if (hardReload) hardReload.current = () => setTick((t) => t + 1);
6495
6504
  }, [hardReload]);
@@ -6498,13 +6507,32 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6498
6507
  const id = setInterval(() => setTick((t) => t + 1), refreshInterval);
6499
6508
  return () => clearInterval(id);
6500
6509
  }, [refreshInterval]);
6510
+ const activeFilterParams = React28.useMemo(() => {
6511
+ const out = {};
6512
+ filterFields?.forEach((f) => {
6513
+ const v = filterValues[f.key];
6514
+ if (f.type === "checkbox" || f.type === "toggle") {
6515
+ if (v) out[f.key] = 1;
6516
+ } else if (f.type === "date-range") {
6517
+ if (v?.from) out[`${f.key}_from`] = v.from;
6518
+ if (v?.to) out[`${f.key}_to`] = v.to;
6519
+ } else if (v !== "" && v !== null && v !== void 0) {
6520
+ out[f.key] = v;
6521
+ }
6522
+ });
6523
+ if (sortKey) {
6524
+ out.sort = sortKey;
6525
+ out.direction = sortDir;
6526
+ }
6527
+ return out;
6528
+ }, [filterValues, sortKey, sortDir, filterFields]);
6501
6529
  React28.useEffect(() => {
6502
6530
  if (manual && tick === 0) return;
6503
6531
  let cancelled = false;
6504
6532
  setLoading(true);
6505
6533
  setError(null);
6506
6534
  import_axios3.default.get(url, {
6507
- params: { ...params, page: currentPage, search: searchValue }
6535
+ params: { ...params, ...activeFilterParams, page: currentPage, search: searchValue }
6508
6536
  }).then(({ data: res }) => {
6509
6537
  if (cancelled) return;
6510
6538
  const payload = encrypt ? decryptLaravelPayload(res, key) : res;
@@ -6550,15 +6578,184 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6550
6578
  return () => {
6551
6579
  cancelled = true;
6552
6580
  };
6553
- }, [url, currentPage, tick, JSON.stringify(params), encrypt, decryptPayloadLog, JSON.stringify(columnOverrides), searchValue]);
6581
+ }, [url, currentPage, tick, JSON.stringify(params), JSON.stringify(activeFilterParams), encrypt, decryptPayloadLog, JSON.stringify(columnOverrides), searchValue]);
6554
6582
  const handleSearchChange = (value) => {
6555
6583
  setSearchValue(value);
6556
6584
  setCurrentPage(1);
6557
6585
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
6558
- debounceTimer.current = setTimeout(() => {
6559
- setTick((t) => t + 1);
6560
- }, debounce);
6586
+ debounceTimer.current = setTimeout(() => setTick((t) => t + 1), debounce);
6587
+ };
6588
+ const handleFilterChange = (key2, value) => {
6589
+ setFilterValues((prev) => ({ ...prev, [key2]: value }));
6590
+ setCurrentPage(1);
6591
+ setTick((t) => t + 1);
6592
+ };
6593
+ const handleClearFilters = () => {
6594
+ const reset = {};
6595
+ filterFields?.forEach((f) => {
6596
+ reset[f.key] = f.type === "checkbox" || f.type === "toggle" ? false : "";
6597
+ });
6598
+ setFilterValues(reset);
6599
+ setSortKey("");
6600
+ setSortDir("asc");
6601
+ setCurrentPage(1);
6602
+ setTick((t) => t + 1);
6561
6603
  };
6604
+ const hasActiveFilters = filterFields?.some((f) => {
6605
+ const v = filterValues[f.key];
6606
+ return f.type === "checkbox" || f.type === "toggle" ? !!v : v !== "" && v !== null && v !== void 0;
6607
+ }) || !!sortKey;
6608
+ const filterBar = filterFields?.length || sortKeys?.length ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap items-end gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3", children: [
6609
+ filterFields?.map((f) => {
6610
+ const label = f.label ?? f.key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
6611
+ const value = filterValues[f.key];
6612
+ const opts = (f.options ?? []).map(
6613
+ (o) => typeof o === "string" ? { label: o, value: o } : o
6614
+ );
6615
+ if (f.type === "checkbox") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
6616
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6617
+ "input",
6618
+ {
6619
+ type: "checkbox",
6620
+ checked: !!value,
6621
+ onChange: (e) => handleFilterChange(f.key, e.target.checked),
6622
+ className: "h-4 w-4 rounded accent-primary"
6623
+ }
6624
+ ),
6625
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs font-medium text-foreground", children: label })
6626
+ ] }, f.key);
6627
+ if (f.type === "toggle") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
6628
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6629
+ "button",
6630
+ {
6631
+ type: "button",
6632
+ role: "switch",
6633
+ "aria-checked": !!value,
6634
+ onClick: () => handleFilterChange(f.key, !value),
6635
+ className: cn(
6636
+ "relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
6637
+ value ? "bg-primary" : "bg-muted"
6638
+ ),
6639
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
6640
+ "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
6641
+ value ? "translate-x-4" : "translate-x-0"
6642
+ ) })
6643
+ }
6644
+ ),
6645
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs font-medium text-foreground", children: label })
6646
+ ] }, f.key);
6647
+ if (f.type === "select") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6648
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6649
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6650
+ "select",
6651
+ {
6652
+ value: value ?? "",
6653
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6654
+ className: "h-8 min-w-[120px] rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
6655
+ children: [
6656
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: "", children: f.placeholder ?? `All ${label}` }),
6657
+ opts.map((o) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: o.value, children: o.label }, o.value))
6658
+ ]
6659
+ }
6660
+ )
6661
+ ] }, f.key);
6662
+ if (f.type === "date" || f.type === "date-time") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6663
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6664
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6665
+ "input",
6666
+ {
6667
+ type: f.type === "date-time" ? "datetime-local" : "date",
6668
+ value: value ?? "",
6669
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6670
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6671
+ }
6672
+ )
6673
+ ] }, f.key);
6674
+ if (f.type === "date-range") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6675
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6676
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
6677
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6678
+ "input",
6679
+ {
6680
+ type: "date",
6681
+ value: value?.from ?? "",
6682
+ onChange: (e) => handleFilterChange(f.key, { ...value, from: e.target.value }),
6683
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6684
+ }
6685
+ ),
6686
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
6687
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6688
+ "input",
6689
+ {
6690
+ type: "date",
6691
+ value: value?.to ?? "",
6692
+ onChange: (e) => handleFilterChange(f.key, { ...value, to: e.target.value }),
6693
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6694
+ }
6695
+ )
6696
+ ] })
6697
+ ] }, f.key);
6698
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6699
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6700
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6701
+ "input",
6702
+ {
6703
+ type: "text",
6704
+ value: value ?? "",
6705
+ placeholder: f.placeholder ?? `Filter ${label}\u2026`,
6706
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6707
+ className: "h-8 min-w-[140px] rounded-lg border border-border bg-background px-3 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6708
+ }
6709
+ )
6710
+ ] }, f.key);
6711
+ }),
6712
+ sortKeys?.length ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6713
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Sort by" }),
6714
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
6715
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6716
+ "select",
6717
+ {
6718
+ value: sortKey,
6719
+ onChange: (e) => {
6720
+ setSortKey(e.target.value);
6721
+ setCurrentPage(1);
6722
+ setTick((t) => t + 1);
6723
+ },
6724
+ className: "h-8 min-w-[120px] rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
6725
+ children: [
6726
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: "", children: "Default" }),
6727
+ sortKeys.map((k) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: k, children: k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) }, k))
6728
+ ]
6729
+ }
6730
+ ),
6731
+ sortKey && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6732
+ "button",
6733
+ {
6734
+ type: "button",
6735
+ onClick: () => {
6736
+ setSortDir((d) => d === "asc" ? "desc" : "asc");
6737
+ setTick((t) => t + 1);
6738
+ },
6739
+ className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border bg-background text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
6740
+ title: sortDir === "asc" ? "Ascending" : "Descending",
6741
+ children: sortDir === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronDown, { className: "h-3.5 w-3.5" })
6742
+ }
6743
+ )
6744
+ ] })
6745
+ ] }) : null,
6746
+ hasActiveFilters && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6747
+ "button",
6748
+ {
6749
+ type: "button",
6750
+ onClick: handleClearFilters,
6751
+ className: "flex h-8 items-center gap-1.5 self-end rounded-lg border border-border bg-background px-3 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
6752
+ children: [
6753
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-3 w-3" }),
6754
+ " Clear"
6755
+ ]
6756
+ }
6757
+ )
6758
+ ] }) : null;
6562
6759
  return {
6563
6760
  data,
6564
6761
  columns,
@@ -6567,6 +6764,7 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6567
6764
  serverPagination: pagination ? { pagination, currentPage, goToPage: (page) => setCurrentPage(page) } : null,
6568
6765
  loading,
6569
6766
  error,
6767
+ filterBar,
6570
6768
  goToPage: (page) => setCurrentPage(page),
6571
6769
  reload: () => setTick((t) => t + 1),
6572
6770
  refresh: () => setTick((t) => t + 1),
@@ -7276,6 +7474,9 @@ function Table({
7276
7474
  renderExpanded,
7277
7475
  columnVisibility,
7278
7476
  onColumnVisibilityChange,
7477
+ columnVisibilityIcon,
7478
+ filterBar,
7479
+ filterableIcon,
7279
7480
  exportable = false,
7280
7481
  onExport,
7281
7482
  virtualized = false,
@@ -7296,6 +7497,8 @@ function Table({
7296
7497
  const [sortKey, setSortKey] = React28.useState(null);
7297
7498
  const [sortDir, setSortDir] = React28.useState(null);
7298
7499
  const [bulkLoading, setBulkLoading] = React28.useState(false);
7500
+ const [bulkConfirm, setBulkConfirm] = React28.useState(null);
7501
+ const [filterBarOpen, setFilterBarOpen] = React28.useState(false);
7299
7502
  const [expandedIds, setExpandedIds] = React28.useState(/* @__PURE__ */ new Set());
7300
7503
  const [dragOverId, setDragOverId] = React28.useState(null);
7301
7504
  const [focusedRowIdx, setFocusedRowIdx] = React28.useState(-1);
@@ -7432,7 +7635,7 @@ function Table({
7432
7635
  const unselectedCount = totalRows - selectedIds.length;
7433
7636
  const handleSelectAllRecords = () => setSelectedIds(filteredData.map((item) => String(item[idKey])));
7434
7637
  const handleUnselectAll = () => setSelectedIds([]);
7435
- const handleBulkDeleteSelected = async () => {
7638
+ const execBulkDeleteSelected = async () => {
7436
7639
  if (!bulkDeleteBaseUrl || selectedIds.length === 0) {
7437
7640
  onBulkDelete?.(selectedIds);
7438
7641
  setSelectedIds([]);
@@ -7454,7 +7657,7 @@ function Table({
7454
7657
  setBulkLoading(false);
7455
7658
  }
7456
7659
  };
7457
- const handleDeleteAll = async () => {
7660
+ const execDeleteAll = async () => {
7458
7661
  if (!bulkDeleteBaseUrl) return;
7459
7662
  setBulkLoading(true);
7460
7663
  try {
@@ -7521,10 +7724,22 @@ function Table({
7521
7724
  ]
7522
7725
  }
7523
7726
  ),
7727
+ unselectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7728
+ "button",
7729
+ {
7730
+ onClick: handleSelectAllRecords,
7731
+ disabled: bulkLoading,
7732
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors disabled:opacity-40",
7733
+ children: [
7734
+ "Select all ",
7735
+ unselectedCount
7736
+ ]
7737
+ }
7738
+ ),
7524
7739
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7525
7740
  "button",
7526
7741
  {
7527
- onClick: handleBulkDeleteSelected,
7742
+ onClick: () => setBulkConfirm("selected"),
7528
7743
  disabled: bulkLoading,
7529
7744
  className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40",
7530
7745
  children: [
@@ -7534,30 +7749,29 @@ function Table({
7534
7749
  " selected"
7535
7750
  ]
7536
7751
  }
7752
+ ),
7753
+ bulkDeleteBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7754
+ "button",
7755
+ {
7756
+ onClick: () => setBulkConfirm("all"),
7757
+ disabled: bulkLoading,
7758
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40",
7759
+ children: [
7760
+ bulkLoading ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7761
+ "Delete all"
7762
+ ]
7763
+ }
7537
7764
  )
7538
7765
  ] }),
7539
- selectable && unselectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7540
- "button",
7541
- {
7542
- onClick: handleSelectAllRecords,
7543
- disabled: bulkLoading,
7544
- className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors disabled:opacity-40",
7545
- children: [
7546
- "Select all ",
7547
- unselectedCount
7548
- ]
7549
- }
7550
- ),
7551
- selectable && bulkDeleteBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7766
+ filterBar && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7552
7767
  "button",
7553
7768
  {
7554
- onClick: handleDeleteAll,
7555
- disabled: bulkLoading,
7556
- className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40",
7557
- children: [
7558
- bulkLoading ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7559
- "Delete all"
7560
- ]
7769
+ onClick: () => setFilterBarOpen((o) => !o),
7770
+ className: cn(
7771
+ "inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors",
7772
+ filterBarOpen ? "border-primary bg-primary/10 text-primary hover:bg-primary/20" : "border-border bg-muted/50 text-muted-foreground hover:bg-muted"
7773
+ ),
7774
+ children: filterableIcon ?? "Filter"
7561
7775
  }
7562
7776
  ),
7563
7777
  exportable && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative group", children: [
@@ -7573,7 +7787,7 @@ function Table({
7573
7787
  )) })
7574
7788
  ] }),
7575
7789
  columnVisibility && onColumnVisibilityChange && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative group", children: [
7576
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors", children: "Columns" }),
7790
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors", children: columnVisibilityIcon ?? "Columns" }),
7577
7791
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "absolute right-0 top-full mt-1 z-20 hidden group-hover:flex flex-col min-w-[150px] rounded-xl border border-border bg-card shadow-lg overflow-hidden p-2 gap-1", children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 px-2 py-1 rounded-lg hover:bg-muted cursor-pointer text-xs", children: [
7578
7792
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7579
7793
  "input",
@@ -7595,6 +7809,7 @@ function Table({
7595
7809
  ] })
7596
7810
  ] })
7597
7811
  ] }),
7812
+ filterBar && filterBarOpen && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { children: filterBar }),
7598
7813
  loading && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-center py-12 text-muted-foreground gap-2", children: [
7599
7814
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-5 w-5 animate-spin" }),
7600
7815
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: "Loading\u2026" })
@@ -7988,6 +8203,37 @@ function Table({
7988
8203
  }
7989
8204
  }
7990
8205
  }
8206
+ ),
8207
+ bulkConfirm && (0, import_react_dom2.createPortal)(
8208
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
8209
+ "div",
8210
+ {
8211
+ className: "fixed inset-0 z-50 flex items-center justify-center p-4",
8212
+ style: { background: "rgba(0,0,0,0.5)" },
8213
+ onMouseDown: (e) => {
8214
+ if (e.target === e.currentTarget) setBulkConfirm(null);
8215
+ },
8216
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative w-full max-w-md rounded-2xl border border-border bg-card shadow-2xl flex flex-col", children: [
8217
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 border-b border-border", children: [
8218
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h2", { className: "text-base font-semibold", children: "Confirm Delete" }),
8219
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { onClick: () => setBulkConfirm(null), className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-4 w-4" }) })
8220
+ ] }),
8221
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "px-6 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-muted-foreground", children: bulkConfirm === "selected" ? `Are you sure you want to delete ${selectedIds.length} selected record${selectedIds.length !== 1 ? "s" : ""}? This action cannot be undone.` : "Are you sure you want to delete all records? This action cannot be undone." }) }),
8222
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "px-6 py-4 border-t border-border flex justify-end gap-2", children: [
8223
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Button, { variant: "outline", size: "sm", onClick: () => setBulkConfirm(null), disabled: bulkLoading, children: "Cancel" }),
8224
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(Button, { variant: "danger", size: "sm", disabled: bulkLoading, onClick: async () => {
8225
+ if (bulkConfirm === "selected") await execBulkDeleteSelected();
8226
+ else await execDeleteAll();
8227
+ setBulkConfirm(null);
8228
+ }, children: [
8229
+ bulkLoading && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 mr-1.5 animate-spin" }),
8230
+ bulkLoading ? "Deleting\u2026" : "Delete"
8231
+ ] })
8232
+ ] })
8233
+ ] })
8234
+ }
8235
+ ),
8236
+ document.body
7991
8237
  )
7992
8238
  ] });
7993
8239
  }
package/dist/index.d.cts CHANGED
@@ -293,6 +293,17 @@ interface UseServerTableOptions {
293
293
  onSuccess?: (data: any[]) => void;
294
294
  /** Called on fetch error */
295
295
  onError?: (error: Error) => void;
296
+ /**
297
+ * Filter fields rendered above the table.
298
+ * Each field appends its value as a query param on every request.
299
+ * Supported types: "input" | "select" | "checkbox" | "toggle" | "date" | "date-time" | "date-range"
300
+ */
301
+ filter?: ServerTableFilterField[];
302
+ /**
303
+ * Sortable column keys. Renders a sort dropdown above the table.
304
+ * Appends sort=key&direction=asc|desc to every request.
305
+ */
306
+ sort?: string[];
296
307
  }
297
308
  /**
298
309
  * Return type from the useServerTable hook
@@ -310,6 +321,8 @@ interface UseServerTableReturn<T> {
310
321
  goToPage: (page: number) => void;
311
322
  reload: () => void;
312
323
  refresh: () => void;
324
+ /** Rendered filter + sort bar — place above <Table /> */
325
+ filterBar: React.ReactNode;
313
326
  searchValue: string;
314
327
  onSearchChange: (value: string) => void;
315
328
  page: number;
@@ -354,6 +367,18 @@ interface ServerPaginationProp {
354
367
  currentPage: number;
355
368
  goToPage: (page: number) => void;
356
369
  }
370
+ type ServerTableFilterType = "input" | "select" | "checkbox" | "toggle" | "date" | "date-time" | "date-range";
371
+ interface ServerTableFilterField {
372
+ key: string;
373
+ type: ServerTableFilterType;
374
+ label?: string;
375
+ placeholder?: string;
376
+ /** Options for type="select" */
377
+ options?: string[] | {
378
+ label: string;
379
+ value: string;
380
+ }[];
381
+ }
357
382
  /**
358
383
  * Custom hook for fetching and managing server-side paginated table data.
359
384
  * Supports Laravel encryption, auto-column derivation, and flexible pagination.
@@ -370,7 +395,7 @@ interface ServerPaginationProp {
370
395
  * })
371
396
  * ```
372
397
  */
373
- declare function useServerTable<T extends Record<string, any>>({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce, transform, manual, refresh: refreshEnabled, refreshInterval, hardReload, onSuccess, onError }: UseServerTableOptions): UseServerTableReturn<T>;
398
+ declare function useServerTable<T extends Record<string, any>>({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce, transform, manual, refresh: refreshEnabled, refreshInterval, hardReload, onSuccess, onError, filter: filterFields, sort: sortKeys }: UseServerTableOptions): UseServerTableReturn<T>;
374
399
  /**
375
400
  * Available field types for action forms (edit/view modals)
376
401
  * @type {ActionFieldType}
@@ -713,6 +738,12 @@ interface TableProps<T> {
713
738
  * - `"soft"` — neumorphic soft shadows, no hard borders
714
739
  */
715
740
  variant?: "default" | "zebra" | "card" | "glass" | "soft";
741
+ /** Custom icon for the column visibility toggle button. When provided, hides the "Columns" text label. */
742
+ columnVisibilityIcon?: React.ReactElement;
743
+ /** Filter bar node (e.g. from useServerTable's filterBar). Rendered below the toolbar when visible. */
744
+ filterBar?: React.ReactNode;
745
+ /** Custom icon for the filter toggle button. When provided, hides the "Filter" text label. */
746
+ filterableIcon?: React.ReactElement;
716
747
  className?: string;
717
748
  }
718
749
  /**
@@ -742,7 +773,7 @@ interface TableProps<T> {
742
773
  * />
743
774
  * ```
744
775
  */
745
- declare function Table<T extends Record<string, any>>({ data, columns, loading, emptyState, error: errorProp, searchable, searchPlaceholder, searchValue: controlledSearch, onSearchChange, clientPagination, itemsPerPage, selectable, onBulkDelete, idKey, bulkDeleteBaseUrl, defaultActions, serverPagination, variant, className, onRowClick, onRowDoubleClick, rowClassName, expandable, renderExpanded, columnVisibility, onColumnVisibilityChange, exportable, onExport, virtualized, draggable, onRowReorder, keyboardNavigation, }: TableProps<T>): react_jsx_runtime.JSX.Element;
776
+ declare function Table<T extends Record<string, any>>({ data, columns, loading, emptyState, error: errorProp, searchable, searchPlaceholder, searchValue: controlledSearch, onSearchChange, clientPagination, itemsPerPage, selectable, onBulkDelete, idKey, bulkDeleteBaseUrl, defaultActions, serverPagination, variant, className, onRowClick, onRowDoubleClick, rowClassName, expandable, renderExpanded, columnVisibility, onColumnVisibilityChange, columnVisibilityIcon, filterBar, filterableIcon, exportable, onExport, virtualized, draggable, onRowReorder, keyboardNavigation, }: TableProps<T>): react_jsx_runtime.JSX.Element;
746
777
 
747
778
  type BulletinPriority = "low" | "medium" | "high" | "urgent";
748
779
  type BulletinLayout = "grid" | "list" | "masonry";
package/dist/index.d.ts CHANGED
@@ -293,6 +293,17 @@ interface UseServerTableOptions {
293
293
  onSuccess?: (data: any[]) => void;
294
294
  /** Called on fetch error */
295
295
  onError?: (error: Error) => void;
296
+ /**
297
+ * Filter fields rendered above the table.
298
+ * Each field appends its value as a query param on every request.
299
+ * Supported types: "input" | "select" | "checkbox" | "toggle" | "date" | "date-time" | "date-range"
300
+ */
301
+ filter?: ServerTableFilterField[];
302
+ /**
303
+ * Sortable column keys. Renders a sort dropdown above the table.
304
+ * Appends sort=key&direction=asc|desc to every request.
305
+ */
306
+ sort?: string[];
296
307
  }
297
308
  /**
298
309
  * Return type from the useServerTable hook
@@ -310,6 +321,8 @@ interface UseServerTableReturn<T> {
310
321
  goToPage: (page: number) => void;
311
322
  reload: () => void;
312
323
  refresh: () => void;
324
+ /** Rendered filter + sort bar — place above <Table /> */
325
+ filterBar: React.ReactNode;
313
326
  searchValue: string;
314
327
  onSearchChange: (value: string) => void;
315
328
  page: number;
@@ -354,6 +367,18 @@ interface ServerPaginationProp {
354
367
  currentPage: number;
355
368
  goToPage: (page: number) => void;
356
369
  }
370
+ type ServerTableFilterType = "input" | "select" | "checkbox" | "toggle" | "date" | "date-time" | "date-range";
371
+ interface ServerTableFilterField {
372
+ key: string;
373
+ type: ServerTableFilterType;
374
+ label?: string;
375
+ placeholder?: string;
376
+ /** Options for type="select" */
377
+ options?: string[] | {
378
+ label: string;
379
+ value: string;
380
+ }[];
381
+ }
357
382
  /**
358
383
  * Custom hook for fetching and managing server-side paginated table data.
359
384
  * Supports Laravel encryption, auto-column derivation, and flexible pagination.
@@ -370,7 +395,7 @@ interface ServerPaginationProp {
370
395
  * })
371
396
  * ```
372
397
  */
373
- declare function useServerTable<T extends Record<string, any>>({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce, transform, manual, refresh: refreshEnabled, refreshInterval, hardReload, onSuccess, onError }: UseServerTableOptions): UseServerTableReturn<T>;
398
+ declare function useServerTable<T extends Record<string, any>>({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce, transform, manual, refresh: refreshEnabled, refreshInterval, hardReload, onSuccess, onError, filter: filterFields, sort: sortKeys }: UseServerTableOptions): UseServerTableReturn<T>;
374
399
  /**
375
400
  * Available field types for action forms (edit/view modals)
376
401
  * @type {ActionFieldType}
@@ -713,6 +738,12 @@ interface TableProps<T> {
713
738
  * - `"soft"` — neumorphic soft shadows, no hard borders
714
739
  */
715
740
  variant?: "default" | "zebra" | "card" | "glass" | "soft";
741
+ /** Custom icon for the column visibility toggle button. When provided, hides the "Columns" text label. */
742
+ columnVisibilityIcon?: React.ReactElement;
743
+ /** Filter bar node (e.g. from useServerTable's filterBar). Rendered below the toolbar when visible. */
744
+ filterBar?: React.ReactNode;
745
+ /** Custom icon for the filter toggle button. When provided, hides the "Filter" text label. */
746
+ filterableIcon?: React.ReactElement;
716
747
  className?: string;
717
748
  }
718
749
  /**
@@ -742,7 +773,7 @@ interface TableProps<T> {
742
773
  * />
743
774
  * ```
744
775
  */
745
- declare function Table<T extends Record<string, any>>({ data, columns, loading, emptyState, error: errorProp, searchable, searchPlaceholder, searchValue: controlledSearch, onSearchChange, clientPagination, itemsPerPage, selectable, onBulkDelete, idKey, bulkDeleteBaseUrl, defaultActions, serverPagination, variant, className, onRowClick, onRowDoubleClick, rowClassName, expandable, renderExpanded, columnVisibility, onColumnVisibilityChange, exportable, onExport, virtualized, draggable, onRowReorder, keyboardNavigation, }: TableProps<T>): react_jsx_runtime.JSX.Element;
776
+ declare function Table<T extends Record<string, any>>({ data, columns, loading, emptyState, error: errorProp, searchable, searchPlaceholder, searchValue: controlledSearch, onSearchChange, clientPagination, itemsPerPage, selectable, onBulkDelete, idKey, bulkDeleteBaseUrl, defaultActions, serverPagination, variant, className, onRowClick, onRowDoubleClick, rowClassName, expandable, renderExpanded, columnVisibility, onColumnVisibilityChange, columnVisibilityIcon, filterBar, filterableIcon, exportable, onExport, virtualized, draggable, onRowReorder, keyboardNavigation, }: TableProps<T>): react_jsx_runtime.JSX.Element;
746
777
 
747
778
  type BulletinPriority = "low" | "medium" | "high" | "urgent";
748
779
  type BulletinLayout = "grid" | "list" | "masonry";