@juv/codego-react-ui 3.4.6 → 3.4.8

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
@@ -5906,11 +5906,77 @@ function FileUpload({
5906
5906
  // src/components/ui/repeater.tsx
5907
5907
  var import_lucide_react15 = require("lucide-react");
5908
5908
  var import_jsx_runtime29 = require("react/jsx-runtime");
5909
+ function RepeaterFieldRenderer({
5910
+ field,
5911
+ value,
5912
+ onChange
5913
+ }) {
5914
+ if (field.type === "image") {
5915
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5916
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5917
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-3", children: [
5918
+ value && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { src: value, alt: field.key, className: "h-10 w-10 rounded-lg object-cover ring-1 ring-border shrink-0" }),
5919
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5920
+ Input,
5921
+ {
5922
+ inputMode: "text",
5923
+ value: value ?? "",
5924
+ onChange: (e) => onChange(e.target.value),
5925
+ placeholder: field.placeholder ?? "Image URL"
5926
+ }
5927
+ )
5928
+ ] })
5929
+ ] });
5930
+ }
5931
+ if (field.type === "attachment") {
5932
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5933
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5934
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
5935
+ value && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
5936
+ "a",
5937
+ {
5938
+ href: value,
5939
+ target: "_blank",
5940
+ rel: "noopener noreferrer",
5941
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted transition-colors shrink-0",
5942
+ children: [
5943
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Paperclip, { className: "h-3 w-3" }),
5944
+ String(value).split("/").pop()
5945
+ ]
5946
+ }
5947
+ ),
5948
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5949
+ Input,
5950
+ {
5951
+ inputMode: "text",
5952
+ value: value ?? "",
5953
+ onChange: (e) => onChange(e.target.value),
5954
+ placeholder: field.placeholder ?? "Attachment URL"
5955
+ }
5956
+ )
5957
+ ] })
5958
+ ] });
5959
+ }
5960
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5961
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5962
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5963
+ Input,
5964
+ {
5965
+ inputMode: "text",
5966
+ value: value ?? "",
5967
+ onChange: (e) => onChange(e.target.value),
5968
+ placeholder: field.placeholder ?? field.key
5969
+ }
5970
+ )
5971
+ ] });
5972
+ }
5909
5973
  function Repeater({
5910
5974
  items,
5911
5975
  onAdd,
5912
5976
  onRemove,
5913
5977
  renderItem,
5978
+ fields,
5979
+ onFieldChange,
5914
5980
  addButtonText = "Add Item",
5915
5981
  className
5916
5982
  }) {
@@ -5923,7 +5989,15 @@ function Repeater({
5923
5989
  children: [
5924
5990
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "mt-1 cursor-grab text-muted-foreground/30 group-hover:text-muted-foreground/60 transition-colors shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.GripVertical, { className: "h-4 w-4" }) }),
5925
5991
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: index + 1 }),
5926
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 min-w-0", children: renderItem(item, index) }),
5992
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 min-w-0", children: fields ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: fields.length > 1 ? `repeat(${Math.min(fields.length, 3)}, minmax(0, 1fr))` : "1fr" }, children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5993
+ RepeaterFieldRenderer,
5994
+ {
5995
+ field: f,
5996
+ value: item[f.key],
5997
+ onChange: (v) => onFieldChange?.(index, f.key, v)
5998
+ },
5999
+ f.key
6000
+ )) }) : renderItem ? renderItem(item, index) : null }),
5927
6001
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
5928
6002
  Button,
5929
6003
  {
@@ -6496,8 +6570,14 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6496
6570
  goToPage: (page) => setCurrentPage(page),
6497
6571
  reload: () => setTick((t) => t + 1),
6498
6572
  refresh: () => setTick((t) => t + 1),
6573
+ // Passthrough props
6499
6574
  searchValue,
6500
- onSearchChange: handleSearchChange
6575
+ onSearchChange: handleSearchChange,
6576
+ page: currentPage,
6577
+ onPageChange: (page) => setCurrentPage(page),
6578
+ sort: [],
6579
+ onSortChange: () => {
6580
+ }
6501
6581
  };
6502
6582
  }
6503
6583
  var MODAL_WIDTH = {
@@ -6653,6 +6733,28 @@ function FieldRenderer({ field, value, onChange }) {
6653
6733
  } });
6654
6734
  case "repeater": {
6655
6735
  const items = Array.isArray(value) ? value : [];
6736
+ if (field.repeaterFields) {
6737
+ const rows = Array.isArray(value) ? value : [];
6738
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6739
+ Repeater,
6740
+ {
6741
+ items: rows,
6742
+ fields: field.repeaterFields,
6743
+ onAdd: () => {
6744
+ const blank = {};
6745
+ field.repeaterFields.forEach((f) => {
6746
+ blank[f.key] = "";
6747
+ });
6748
+ onChange([...rows, blank]);
6749
+ },
6750
+ onRemove: (i) => onChange(rows.filter((_, idx) => idx !== i)),
6751
+ onFieldChange: (i, key, val) => {
6752
+ const next = rows.map((r, idx) => idx === i ? { ...r, [key]: val } : r);
6753
+ onChange(next);
6754
+ }
6755
+ }
6756
+ );
6757
+ }
6656
6758
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6657
6759
  Repeater,
6658
6760
  {
@@ -6693,8 +6795,172 @@ function ViewModal({
6693
6795
  item,
6694
6796
  fields,
6695
6797
  onClose,
6696
- width
6798
+ width,
6799
+ grid
6697
6800
  }) {
6801
+ const renderViewValue = (f, value) => {
6802
+ const sizeStyle = {
6803
+ ...f.width ? { width: typeof f.width === "number" ? `${f.width}px` : f.width } : {},
6804
+ ...f.height ? { height: typeof f.height === "number" ? `${f.height}px` : f.height } : {}
6805
+ };
6806
+ const vt = f.viewType ?? f.type;
6807
+ const empty = value === null || value === void 0 || value === "";
6808
+ const dash = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-muted-foreground italic text-sm", children: "\u2014" });
6809
+ if (f.component) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.component });
6810
+ if (f.render) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.render(value, () => {
6811
+ }) });
6812
+ switch (vt) {
6813
+ case "image":
6814
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6815
+ "img",
6816
+ {
6817
+ src: value,
6818
+ alt: f.label,
6819
+ className: "rounded-xl object-cover ring-1 ring-border",
6820
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 }
6821
+ }
6822
+ );
6823
+ case "image-url":
6824
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6825
+ "a",
6826
+ {
6827
+ href: value,
6828
+ onClick: (e) => e.preventDefault(),
6829
+ className: "inline-block rounded-xl overflow-hidden ring-1 ring-border hover:ring-primary transition-all",
6830
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 },
6831
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: value, alt: f.label, className: "w-full h-full object-cover" })
6832
+ }
6833
+ );
6834
+ case "image-url-open-other-tabs":
6835
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6836
+ "a",
6837
+ {
6838
+ href: value,
6839
+ target: "_blank",
6840
+ rel: "noopener noreferrer",
6841
+ className: "inline-block rounded-xl overflow-hidden ring-1 ring-border hover:ring-primary transition-all",
6842
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 },
6843
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: value, alt: f.label, className: "w-full h-full object-cover" })
6844
+ }
6845
+ );
6846
+ case "text-url":
6847
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6848
+ "a",
6849
+ {
6850
+ href: value,
6851
+ onClick: (e) => e.preventDefault(),
6852
+ className: "text-sm text-primary underline underline-offset-2 hover:text-primary/80 break-all",
6853
+ style: sizeStyle,
6854
+ children: value
6855
+ }
6856
+ );
6857
+ case "text-url-open-other-tabs":
6858
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6859
+ "a",
6860
+ {
6861
+ href: value,
6862
+ target: "_blank",
6863
+ rel: "noopener noreferrer",
6864
+ className: "text-sm text-primary underline underline-offset-2 hover:text-primary/80 break-all",
6865
+ style: sizeStyle,
6866
+ children: value
6867
+ }
6868
+ );
6869
+ case "attachment":
6870
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6871
+ "a",
6872
+ {
6873
+ href: value,
6874
+ target: "_blank",
6875
+ rel: "noopener noreferrer",
6876
+ 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-foreground hover:bg-muted transition-colors",
6877
+ style: sizeStyle,
6878
+ children: [
6879
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("svg", { className: "h-3.5 w-3.5 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" }) }),
6880
+ String(value).split("/").pop() ?? "Download"
6881
+ ]
6882
+ }
6883
+ );
6884
+ case "repeater": {
6885
+ const rows = Array.isArray(value) ? value : [];
6886
+ if (!rows.length) return dash;
6887
+ const rFields = f.repeaterFields;
6888
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "space-y-2", children: rows.map((row, ri) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap gap-3 rounded-xl border border-border bg-muted/30 px-3 py-2", children: [
6889
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: ri + 1 }),
6890
+ rFields ? rFields.map((rf) => {
6891
+ const v = row[rf.key];
6892
+ if (rf.type === "image") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6893
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
6894
+ v ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: v, alt: rf.key, className: "h-10 w-10 rounded-lg object-cover ring-1 ring-border" }) : dash
6895
+ ] }, rf.key);
6896
+ if (rf.type === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6897
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
6898
+ v ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6899
+ "a",
6900
+ {
6901
+ href: v,
6902
+ target: "_blank",
6903
+ rel: "noopener noreferrer",
6904
+ className: "inline-flex items-center gap-1 rounded-lg border border-border bg-muted/50 px-2 py-1 text-xs font-medium hover:bg-muted transition-colors",
6905
+ children: [
6906
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("svg", { className: "h-3 w-3 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" }) }),
6907
+ String(v).split("/").pop()
6908
+ ]
6909
+ }
6910
+ ) : dash
6911
+ ] }, rf.key);
6912
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6913
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
6914
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: v ?? "\u2014" })
6915
+ ] }, rf.key);
6916
+ }) : (
6917
+ // payload mode: row has { type, key, value }
6918
+ Object.entries(row).map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6919
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: k }),
6920
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: String(v) })
6921
+ ] }, k))
6922
+ )
6923
+ ] }, ri)) });
6924
+ }
6925
+ case "checkbox":
6926
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6927
+ "input",
6928
+ {
6929
+ type: "checkbox",
6930
+ checked: !!value,
6931
+ readOnly: true,
6932
+ className: "h-4 w-4 rounded border-border accent-primary cursor-default",
6933
+ style: sizeStyle
6934
+ }
6935
+ );
6936
+ case "toggle":
6937
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6938
+ "div",
6939
+ {
6940
+ className: cn(
6941
+ "relative inline-flex shrink-0 rounded-full border-2 border-transparent transition-colors",
6942
+ value ? "bg-primary" : "bg-muted"
6943
+ ),
6944
+ style: { width: sizeStyle.width ?? 36, height: sizeStyle.height ?? 20 },
6945
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6946
+ "span",
6947
+ {
6948
+ className: cn(
6949
+ "pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform"
6950
+ ),
6951
+ style: {
6952
+ width: typeof (sizeStyle.height ?? 20) === "number" ? sizeStyle.height - 4 : 16,
6953
+ height: typeof (sizeStyle.height ?? 20) === "number" ? sizeStyle.height - 4 : 16,
6954
+ transform: value ? `translateX(${typeof (sizeStyle.width ?? 36) === "number" ? sizeStyle.width - (sizeStyle.height ?? 20) : 16}px)` : "translateX(0)"
6955
+ }
6956
+ }
6957
+ )
6958
+ }
6959
+ );
6960
+ default:
6961
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground break-words", style: sizeStyle, children: String(value) });
6962
+ }
6963
+ };
6698
6964
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6699
6965
  ModalShell,
6700
6966
  {
@@ -6702,11 +6968,27 @@ function ViewModal({
6702
6968
  onClose,
6703
6969
  width,
6704
6970
  footer: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Button, { variant: "outline", size: "sm", onClick: onClose, children: "Close" }),
6705
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "space-y-3", children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { children: [
6706
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs font-semibold text-muted-foreground mb-1", children: f.label }),
6707
- f.component ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.component }) : f.render ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.render(item[f.key], () => {
6708
- }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground break-words", children: item[f.key] === null || item[f.key] === void 0 || item[f.key] === "" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-muted-foreground italic", children: "\u2014" }) : String(item[f.key]) })
6709
- ] }, f.key)) })
6971
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6972
+ "div",
6973
+ {
6974
+ className: grid ? "grid gap-4" : "space-y-3",
6975
+ style: grid ? { gridTemplateColumns: `repeat(${grid}, minmax(0, 1fr))` } : void 0,
6976
+ children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6977
+ "div",
6978
+ {
6979
+ style: {
6980
+ ...f.colSpan ? { gridColumn: `span ${f.colSpan}` } : {},
6981
+ ...f.rowSpan ? { gridRow: `span ${f.rowSpan}` } : {}
6982
+ },
6983
+ children: [
6984
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs font-semibold text-muted-foreground mb-1", children: f.label }),
6985
+ renderViewValue(f, item[f.key])
6986
+ ]
6987
+ },
6988
+ f.key
6989
+ ))
6990
+ }
6991
+ )
6710
6992
  }
6711
6993
  );
6712
6994
  }
@@ -6939,26 +7221,84 @@ var BADGE_COLORS = {
6939
7221
  function badgeClass(value) {
6940
7222
  return BADGE_COLORS[value.toLowerCase()] ?? "bg-primary/10 text-primary border-primary/20";
6941
7223
  }
7224
+ function deriveField(key, sample) {
7225
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
7226
+ const base = { key, label };
7227
+ if (typeof sample === "boolean") return { ...base, type: "toggle", viewType: "toggle" };
7228
+ if (typeof sample === "number") return { ...base, inputType: "number" };
7229
+ if (Array.isArray(sample)) {
7230
+ if (sample.length === 0 || typeof sample[0] === "string")
7231
+ return { ...base, type: "tag-input" };
7232
+ return base;
7233
+ }
7234
+ if (typeof sample === "string") {
7235
+ if (/\.(png|jpe?g|gif|webp|svg|avif)(\?.*)?$/i.test(sample))
7236
+ return { ...base, type: "input", viewType: "image" };
7237
+ if (/\.(pdf|docx?|xlsx?|csv|zip|pptx?)(\?.*)?$/i.test(sample))
7238
+ return { ...base, type: "input", viewType: "attachment" };
7239
+ if (/^https?:\/\//.test(sample))
7240
+ return { ...base, type: "input", viewType: "text-url-open-other-tabs" };
7241
+ if (sample.length > 120 || /\n/.test(sample))
7242
+ return { ...base, type: "textarea" };
7243
+ if (/password|secret|token/i.test(key))
7244
+ return { ...base, type: "password" };
7245
+ if (/email/i.test(key))
7246
+ return { ...base, inputType: "email" };
7247
+ if (/color|colour/i.test(key) && /^#[0-9a-f]{3,8}$/i.test(sample))
7248
+ return { ...base, type: "color-picker", viewType: "text" };
7249
+ }
7250
+ return base;
7251
+ }
6942
7252
  function Table({
6943
7253
  data,
6944
7254
  columns,
7255
+ loading,
7256
+ emptyState,
7257
+ error: errorProp,
6945
7258
  searchable = false,
6946
7259
  searchPlaceholder = "Search...",
6947
- pagination = false,
7260
+ searchValue: controlledSearch,
7261
+ onSearchChange,
7262
+ clientPagination = false,
6948
7263
  itemsPerPage = 10,
6949
7264
  selectable = false,
6950
7265
  onBulkDelete,
6951
7266
  idKey = "id",
7267
+ bulkDeleteBaseUrl,
6952
7268
  defaultActions,
6953
7269
  serverPagination,
6954
- className
7270
+ variant = "default",
7271
+ className,
7272
+ onRowClick,
7273
+ onRowDoubleClick,
7274
+ rowClassName,
7275
+ expandable = false,
7276
+ renderExpanded,
7277
+ columnVisibility,
7278
+ onColumnVisibilityChange,
7279
+ exportable = false,
7280
+ onExport,
7281
+ virtualized = false,
7282
+ draggable = false,
7283
+ onRowReorder,
7284
+ keyboardNavigation = false
6955
7285
  }) {
6956
7286
  const { toast } = useToast();
6957
- const [search, setSearch] = React28.useState("");
7287
+ const isControlledSearch = controlledSearch !== void 0;
7288
+ const [internalSearch, setInternalSearch] = React28.useState("");
7289
+ const search = isControlledSearch ? controlledSearch : internalSearch;
7290
+ const setSearch = (v) => {
7291
+ if (!isControlledSearch) setInternalSearch(v);
7292
+ onSearchChange?.(v);
7293
+ };
6958
7294
  const [currentPage, setCurrentPage] = React28.useState(1);
6959
7295
  const [selectedIds, setSelectedIds] = React28.useState([]);
6960
7296
  const [sortKey, setSortKey] = React28.useState(null);
6961
7297
  const [sortDir, setSortDir] = React28.useState(null);
7298
+ const [bulkLoading, setBulkLoading] = React28.useState(false);
7299
+ const [expandedIds, setExpandedIds] = React28.useState(/* @__PURE__ */ new Set());
7300
+ const [dragOverId, setDragOverId] = React28.useState(null);
7301
+ const [focusedRowIdx, setFocusedRowIdx] = React28.useState(-1);
6962
7302
  const [viewItem, setViewItem] = React28.useState(null);
6963
7303
  const [editItem, setEditItem] = React28.useState(null);
6964
7304
  const [deleteItem, setDeleteItem] = React28.useState(null);
@@ -6970,15 +7310,17 @@ function Table({
6970
7310
  const safeBaseUrl = defaultActions?.baseUrl.replace(/\/+$/, "") ?? "";
6971
7311
  const autoFields = React28.useMemo(() => {
6972
7312
  if (!tableData.length) return [];
6973
- return Object.keys(tableData[0]).map((k) => ({
6974
- key: k,
6975
- label: k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
6976
- }));
7313
+ const row = tableData[0];
7314
+ return Object.keys(row).map((k) => deriveField(k, row[k]));
6977
7315
  }, [tableData]);
6978
7316
  const editFields = defaultActions?.editForm ?? autoFields;
6979
7317
  const viewFields = defaultActions?.viewForm ?? autoFields;
7318
+ const visibleColumns = React28.useMemo(() => {
7319
+ if (!columnVisibility) return columns;
7320
+ return columns.filter((col) => columnVisibility[String(col.key)] !== false);
7321
+ }, [columns, columnVisibility]);
6980
7322
  const allColumns = React28.useMemo(() => {
6981
- if (!defaultActions) return columns;
7323
+ if (!defaultActions) return visibleColumns;
6982
7324
  const actionsCol = {
6983
7325
  key: "__actions__",
6984
7326
  title: "Actions",
@@ -7027,8 +7369,8 @@ function Table({
7027
7369
  ))
7028
7370
  ] })
7029
7371
  };
7030
- return defaultActions.position === "first" ? [actionsCol, ...columns] : [...columns, actionsCol];
7031
- }, [columns, defaultActions]);
7372
+ return defaultActions.position === "first" ? [actionsCol, ...visibleColumns] : [...visibleColumns, actionsCol];
7373
+ }, [visibleColumns, defaultActions]);
7032
7374
  const handleSort = (key) => {
7033
7375
  if (sortKey !== key) {
7034
7376
  setSortKey(key);
@@ -7061,16 +7403,74 @@ function Table({
7061
7403
  const totalPages = Math.max(1, Math.ceil(filteredData.length / itemsPerPage));
7062
7404
  const safePage = Math.min(currentPage, totalPages);
7063
7405
  const paginatedData = React28.useMemo(() => {
7064
- if (!pagination) return filteredData;
7406
+ if (!clientPagination) return filteredData;
7065
7407
  const start = (safePage - 1) * itemsPerPage;
7066
7408
  return filteredData.slice(start, start + itemsPerPage);
7067
- }, [filteredData, pagination, safePage, itemsPerPage]);
7409
+ }, [filteredData, clientPagination, safePage, itemsPerPage]);
7068
7410
  React28.useEffect(() => {
7069
7411
  setCurrentPage(1);
7070
7412
  }, [search]);
7413
+ React28.useEffect(() => {
7414
+ if (!keyboardNavigation) return;
7415
+ const handler = (e) => {
7416
+ if (e.key === "ArrowDown") {
7417
+ e.preventDefault();
7418
+ setFocusedRowIdx((i) => Math.min(i + 1, paginatedData.length - 1));
7419
+ }
7420
+ if (e.key === "ArrowUp") {
7421
+ e.preventDefault();
7422
+ setFocusedRowIdx((i) => Math.max(i - 1, 0));
7423
+ }
7424
+ };
7425
+ window.addEventListener("keydown", handler);
7426
+ return () => window.removeEventListener("keydown", handler);
7427
+ }, [keyboardNavigation, paginatedData.length]);
7071
7428
  const handleSelectAll = (checked) => setSelectedIds(checked ? paginatedData.map((item) => String(item[idKey])) : []);
7072
7429
  const handleSelect = (id, checked) => setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((i) => i !== id));
7073
7430
  const allSelected = paginatedData.length > 0 && selectedIds.length === paginatedData.length;
7431
+ const totalRows = serverPagination ? serverPagination.pagination.total : filteredData.length;
7432
+ const unselectedCount = totalRows - selectedIds.length;
7433
+ const handleSelectAllRecords = () => setSelectedIds(filteredData.map((item) => String(item[idKey])));
7434
+ const handleUnselectAll = () => setSelectedIds([]);
7435
+ const handleBulkDeleteSelected = async () => {
7436
+ if (!bulkDeleteBaseUrl || selectedIds.length === 0) {
7437
+ onBulkDelete?.(selectedIds);
7438
+ setSelectedIds([]);
7439
+ return;
7440
+ }
7441
+ setBulkLoading(true);
7442
+ try {
7443
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7444
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7445
+ const safeUrl = bulkDeleteBaseUrl.replace(/\/+$/, "");
7446
+ await import_axios3.default.delete(`${safeUrl}/delete/${selectedIds.join(",")}/selected`, { headers: { "X-CSRF-Token": csrfToken } });
7447
+ setTableData((prev) => prev.filter((r) => !selectedIds.includes(String(r[idKey]))));
7448
+ onBulkDelete?.(selectedIds);
7449
+ setSelectedIds([]);
7450
+ defaultActions?.onReload?.();
7451
+ } catch (err) {
7452
+ console.error("[Table] Bulk delete selected failed:", err?.response?.data?.message ?? err.message);
7453
+ } finally {
7454
+ setBulkLoading(false);
7455
+ }
7456
+ };
7457
+ const handleDeleteAll = async () => {
7458
+ if (!bulkDeleteBaseUrl) return;
7459
+ setBulkLoading(true);
7460
+ try {
7461
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7462
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7463
+ const safeUrl = bulkDeleteBaseUrl.replace(/\/+$/, "");
7464
+ await import_axios3.default.delete(`${safeUrl}/delete/all`, { headers: { "X-CSRF-Token": csrfToken } });
7465
+ setTableData([]);
7466
+ setSelectedIds([]);
7467
+ defaultActions?.onReload?.();
7468
+ } catch (err) {
7469
+ console.error("[Table] Delete all failed:", err?.response?.data?.message ?? err.message);
7470
+ } finally {
7471
+ setBulkLoading(false);
7472
+ }
7473
+ };
7074
7474
  const pagePills = React28.useMemo(() => {
7075
7475
  if (totalPages <= 5) return Array.from({ length: totalPages }, (_, i) => i + 1);
7076
7476
  if (safePage <= 3) return [1, 2, 3, 4, 5];
@@ -7084,6 +7484,7 @@ function Table({
7084
7484
  };
7085
7485
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
7086
7486
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: cn("w-full space-y-3", className), children: [
7487
+ errorProp && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "rounded-xl border border-danger/30 bg-danger/5 px-4 py-3 text-sm text-danger", children: errorProp }),
7087
7488
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [
7088
7489
  searchable && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative w-72", children: [
7089
7490
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Search, { className: "absolute text-primary left-3 top-1/2 -translate-y-1/2 h-4 w-4 z-10" }),
@@ -7105,33 +7506,115 @@ function Table({
7105
7506
  }
7106
7507
  )
7107
7508
  ] }),
7108
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2 ml-auto", children: [
7109
- selectable && onBulkDelete && selectedIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7509
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2 ml-auto flex-wrap", children: [
7510
+ selectable && selectedIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
7511
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7512
+ "button",
7513
+ {
7514
+ onClick: handleUnselectAll,
7515
+ disabled: bulkLoading,
7516
+ 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",
7517
+ children: [
7518
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-3.5 w-3.5" }),
7519
+ "Unselect all ",
7520
+ selectedIds.length
7521
+ ]
7522
+ }
7523
+ ),
7524
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7525
+ "button",
7526
+ {
7527
+ onClick: handleBulkDeleteSelected,
7528
+ disabled: bulkLoading,
7529
+ 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
+ children: [
7531
+ 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" }),
7532
+ "Delete ",
7533
+ selectedIds.length,
7534
+ " selected"
7535
+ ]
7536
+ }
7537
+ )
7538
+ ] }),
7539
+ selectable && unselectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7110
7540
  "button",
7111
7541
  {
7112
- onClick: () => {
7113
- onBulkDelete(selectedIds);
7114
- setSelectedIds([]);
7115
- },
7116
- 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",
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)(
7552
+ "button",
7553
+ {
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",
7117
7557
  children: [
7118
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7119
- "Delete ",
7120
- selectedIds.length,
7121
- " selected"
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"
7122
7560
  ]
7123
7561
  }
7124
7562
  ),
7563
+ exportable && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative group", children: [
7564
+ /* @__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: "Export" }),
7565
+ /* @__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-[110px] rounded-xl border border-border bg-card shadow-lg overflow-hidden", children: ["csv", "excel", "pdf"].map((type) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7566
+ "button",
7567
+ {
7568
+ onClick: () => onExport?.(type),
7569
+ className: "px-4 py-2 text-xs text-left hover:bg-muted transition-colors capitalize",
7570
+ children: type.toUpperCase()
7571
+ },
7572
+ type
7573
+ )) })
7574
+ ] }),
7575
+ 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" }),
7577
+ /* @__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
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7579
+ "input",
7580
+ {
7581
+ type: "checkbox",
7582
+ checked: columnVisibility[String(col.key)] !== false,
7583
+ onChange: (e) => onColumnVisibilityChange({ ...columnVisibility, [String(col.key)]: e.target.checked }),
7584
+ className: "accent-primary"
7585
+ }
7586
+ ),
7587
+ col.title
7588
+ ] }, String(col.key))) })
7589
+ ] }),
7125
7590
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7126
- serverPagination ? serverPagination.pagination.total : filteredData.length,
7591
+ totalRows,
7127
7592
  " ",
7128
- (serverPagination ? serverPagination.pagination.total : filteredData.length) === 1 ? "row" : "rows",
7593
+ totalRows === 1 ? "row" : "rows",
7129
7594
  search && ` \xB7 filtered from ${tableData.length}`
7130
7595
  ] })
7131
7596
  ] })
7132
7597
  ] }),
7133
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "w-full overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("table", { className: "w-full caption-bottom text-sm", children: [
7134
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: "border-b border-border bg-muted/40", children: [
7598
+ loading && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-center py-12 text-muted-foreground gap-2", children: [
7599
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-5 w-5 animate-spin" }),
7600
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: "Loading\u2026" })
7601
+ ] }),
7602
+ !loading && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn(
7603
+ variant === "default" && "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm",
7604
+ variant === "zebra" && "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm",
7605
+ variant === "card" && "space-y-2",
7606
+ variant === "glass" && "rounded-2xl overflow-hidden border border-white/10 bg-background/30 backdrop-blur-xl shadow-xl",
7607
+ variant === "soft" && "rounded-2xl overflow-hidden bg-card",
7608
+ variant === "soft" && "[box-shadow:6px_6px_12px_hsl(var(--foreground)/0.07),-6px_-6px_12px_hsl(var(--background)/0.8)]",
7609
+ virtualized && "max-h-[520px] overflow-y-auto"
7610
+ ), children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn("w-full overflow-auto", variant === "card" && "space-y-2"), children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("table", { className: cn("w-full caption-bottom text-sm", variant === "card" && "border-separate border-spacing-y-2"), children: [
7611
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: cn(
7612
+ variant === "default" && "border-b border-border bg-muted/40",
7613
+ variant === "zebra" && "border-b border-border bg-muted/40",
7614
+ variant === "card" && "[&>th]:bg-transparent",
7615
+ variant === "glass" && "border-b border-white/10 bg-white/5",
7616
+ variant === "soft" && "border-b-0 bg-muted/30"
7617
+ ), children: [
7135
7618
  selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { className: "h-11 w-[46px] px-4 text-left align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7136
7619
  Checkbox,
7137
7620
  {
@@ -7139,13 +7622,16 @@ function Table({
7139
7622
  onChange: (e) => handleSelectAll(e.target.checked)
7140
7623
  }
7141
7624
  ) }),
7625
+ expandable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { className: "h-11 w-8" }),
7142
7626
  allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7143
7627
  "th",
7144
7628
  {
7145
7629
  onClick: () => col.sortable && handleSort(String(col.key)),
7146
7630
  className: cn(
7147
7631
  "h-11 px-4 text-left align-middle text-xs font-semibold uppercase tracking-wider text-muted-foreground select-none whitespace-nowrap",
7148
- col.sortable && "cursor-pointer hover:text-foreground transition-colors"
7632
+ col.sortable && "cursor-pointer hover:text-foreground transition-colors",
7633
+ variant === "glass" && "text-foreground/70",
7634
+ variant === "soft" && "text-muted-foreground/80"
7149
7635
  ),
7150
7636
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "inline-flex items-center", children: [
7151
7637
  col.title,
@@ -7158,9 +7644,9 @@ function Table({
7158
7644
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tbody", { children: paginatedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7159
7645
  "td",
7160
7646
  {
7161
- colSpan: allColumns.length + (selectable ? 1 : 0),
7647
+ colSpan: allColumns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
7162
7648
  className: "h-32 text-center align-middle",
7163
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col items-center gap-1 text-muted-foreground", children: [
7649
+ children: emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col items-center gap-1 text-muted-foreground", children: [
7164
7650
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Search, { className: "h-8 w-8 opacity-20" }),
7165
7651
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: "No results found" }),
7166
7652
  search && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { onClick: () => setSearch(""), className: "text-xs text-primary hover:underline", children: "Clear search" })
@@ -7169,85 +7655,168 @@ function Table({
7169
7655
  ) }) : paginatedData.map((item, i) => {
7170
7656
  const id = String(item[idKey] || i);
7171
7657
  const isSelected = selectedIds.includes(id);
7172
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7173
- "tr",
7174
- {
7175
- className: cn(
7176
- "border-b border-border/60 transition-colors last:border-0",
7177
- isSelected ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"
7178
- ),
7179
- children: [
7180
- selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7181
- Checkbox,
7182
- {
7183
- checked: isSelected,
7184
- onChange: (e) => handleSelect(id, e.target.checked)
7185
- }
7186
- ) }),
7187
- allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: col.render ? col.render(item) : col.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7188
- "img",
7189
- {
7190
- src: item[col.key],
7191
- alt: item[col.key],
7192
- className: "h-9 w-9 rounded-lg object-cover ring-1 ring-border"
7193
- }
7194
- ) : col.type === "badge" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cn(
7195
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium",
7196
- badgeClass(String(item[col.key]))
7197
- ), children: [
7198
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7199
- "mr-1.5 h-1.5 w-1.5 rounded-full",
7200
- badgeClass(String(item[col.key])).includes("success") ? "bg-success" : badgeClass(String(item[col.key])).includes("warning") ? "bg-warning" : badgeClass(String(item[col.key])).includes("danger") ? "bg-danger" : badgeClass(String(item[col.key])).includes("info") ? "bg-info" : "bg-primary"
7658
+ const isExpanded = expandedIds.has(id);
7659
+ const isFocused = keyboardNavigation && focusedRowIdx === i;
7660
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(React28.Fragment, { children: [
7661
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7662
+ "tr",
7663
+ {
7664
+ draggable,
7665
+ tabIndex: keyboardNavigation ? 0 : void 0,
7666
+ onDragStart: draggable ? (e) => {
7667
+ e.dataTransfer.setData("text/plain", id);
7668
+ } : void 0,
7669
+ onDragOver: draggable ? (e) => {
7670
+ e.preventDefault();
7671
+ setDragOverId(id);
7672
+ } : void 0,
7673
+ onDragLeave: draggable ? () => setDragOverId(null) : void 0,
7674
+ onDrop: draggable ? (e) => {
7675
+ e.preventDefault();
7676
+ setDragOverId(null);
7677
+ const fromId = e.dataTransfer.getData("text/plain");
7678
+ if (fromId === id) return;
7679
+ setTableData((prev) => {
7680
+ const fromIdx = prev.findIndex((r) => String(r[idKey] || "") === fromId);
7681
+ const toIdx = prev.findIndex((r) => String(r[idKey] || "") === id);
7682
+ if (fromIdx < 0 || toIdx < 0) return prev;
7683
+ const next = [...prev];
7684
+ const [moved] = next.splice(fromIdx, 1);
7685
+ next.splice(toIdx, 0, moved);
7686
+ onRowReorder?.(next);
7687
+ return next;
7688
+ });
7689
+ } : void 0,
7690
+ onClick: () => {
7691
+ if (expandable) setExpandedIds((prev) => {
7692
+ const s = new Set(prev);
7693
+ s.has(id) ? s.delete(id) : s.add(id);
7694
+ return s;
7695
+ });
7696
+ onRowClick?.(item);
7697
+ if (keyboardNavigation) setFocusedRowIdx(i);
7698
+ },
7699
+ onDoubleClick: () => onRowDoubleClick?.(item),
7700
+ className: cn(
7701
+ // default
7702
+ variant === "default" && "border-b border-border/60 transition-colors last:border-0",
7703
+ variant === "default" && (isSelected ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"),
7704
+ // zebra
7705
+ variant === "zebra" && "border-b border-border/40 transition-colors last:border-0",
7706
+ variant === "zebra" && (isSelected ? "bg-primary/8" : i % 2 === 0 ? "bg-card" : "bg-muted/40"),
7707
+ variant === "zebra" && !isSelected && "hover:bg-primary/5",
7708
+ // card
7709
+ variant === "card" && "rounded-xl border border-border bg-card shadow-sm transition-all hover:shadow-md hover:-translate-y-px",
7710
+ variant === "card" && (isSelected ? "border-primary/50 bg-primary/5" : ""),
7711
+ variant === "card" && "[&>td:first-child]:rounded-l-xl [&>td:last-child]:rounded-r-xl",
7712
+ // glass
7713
+ variant === "glass" && "border-b border-white/8 transition-colors last:border-0",
7714
+ variant === "glass" && (isSelected ? "bg-primary/15 hover:bg-primary/20" : "hover:bg-white/5"),
7715
+ // soft
7716
+ variant === "soft" && "transition-all",
7717
+ variant === "soft" && (isSelected ? "bg-primary/8 [box-shadow:inset_2px_2px_5px_hsl(var(--foreground)/0.06),inset_-2px_-2px_5px_hsl(var(--background)/0.7)]" : "hover:bg-muted/20"),
7718
+ variant === "soft" && "border-b border-border/30 last:border-0",
7719
+ (onRowClick || onRowDoubleClick || expandable) && "cursor-pointer",
7720
+ draggable && dragOverId === id && "ring-2 ring-inset ring-primary/40",
7721
+ isFocused && "ring-2 ring-inset ring-ring",
7722
+ rowClassName?.(item)
7723
+ ),
7724
+ children: [
7725
+ selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7726
+ Checkbox,
7727
+ {
7728
+ checked: isSelected,
7729
+ onChange: (e) => handleSelect(id, e.target.checked)
7730
+ }
7201
7731
  ) }),
7202
- item[col.key]
7203
- ] }) : col.type === "stack" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AvatarStack, { images: Array.isArray(item[col.key]) ? item[col.key] : [], ...col.stackProps ?? {} }) : col.type === "icon" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex items-center", children: item[col.key] }) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7204
- "select",
7205
- {
7206
- value: item[col.key],
7207
- onChange: (e) => col.onChange?.(item, e.target.value),
7208
- className: "h-8 rounded-lg border border-border bg-background/50 px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
7209
- children: (col.selectOptions ?? []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: opt, children: opt }, opt))
7210
- }
7211
- ) : col.type === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7212
- "button",
7213
- {
7214
- role: "switch",
7215
- "aria-checked": !!item[col.key],
7216
- onClick: () => col.onChange?.(item, !item[col.key]),
7217
- className: cn(
7218
- "relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7219
- item[col.key] ? "bg-primary" : "bg-muted"
7220
- ),
7221
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7222
- "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
7223
- item[col.key] ? "translate-x-4" : "translate-x-0"
7224
- ) })
7225
- }
7226
- ) : col.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2", children: [
7227
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7228
- "input",
7732
+ expandable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "w-8 px-2 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronRight, { className: cn("h-3.5 w-3.5 text-muted-foreground transition-transform", isExpanded && "rotate-90") }) }),
7733
+ allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: col.render ? col.render(item) : col.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7734
+ "img",
7229
7735
  {
7230
- type: "color",
7231
- value: item[col.key] || "#000000",
7736
+ src: item[col.key],
7737
+ alt: item[col.key],
7738
+ className: "h-9 w-9 rounded-lg object-cover ring-1 ring-border"
7739
+ }
7740
+ ) : col.type === "badge" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cn(
7741
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium",
7742
+ badgeClass(String(item[col.key]))
7743
+ ), children: [
7744
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7745
+ "mr-1.5 h-1.5 w-1.5 rounded-full",
7746
+ badgeClass(String(item[col.key])).includes("success") ? "bg-success" : badgeClass(String(item[col.key])).includes("warning") ? "bg-warning" : badgeClass(String(item[col.key])).includes("danger") ? "bg-danger" : badgeClass(String(item[col.key])).includes("info") ? "bg-info" : "bg-primary"
7747
+ ) }),
7748
+ item[col.key]
7749
+ ] }) : col.type === "stack" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AvatarStack, { images: Array.isArray(item[col.key]) ? item[col.key] : [], ...col.stackProps ?? {} }) : col.type === "icon" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex items-center", children: item[col.key] }) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7750
+ "select",
7751
+ {
7752
+ value: item[col.key],
7232
7753
  onChange: (e) => col.onChange?.(item, e.target.value),
7233
- className: "h-7 w-7 cursor-pointer rounded border border-border bg-transparent p-0.5"
7754
+ className: "h-8 rounded-lg border border-border bg-background/50 px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
7755
+ children: (col.selectOptions ?? []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: opt, children: opt }, opt))
7234
7756
  }
7235
- ),
7236
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground font-mono", children: item[col.key] })
7237
- ] }) : col.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7238
- Checkbox,
7239
- {
7240
- checked: !!item[col.key],
7241
- onChange: (e) => col.onChange?.(item, e.target.checked)
7242
- }
7243
- ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-foreground/90", children: item[col.key] }) }, `${String(col.key)}-${ci}`))
7244
- ]
7245
- },
7246
- id
7247
- );
7757
+ ) : col.type === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7758
+ "button",
7759
+ {
7760
+ role: "switch",
7761
+ "aria-checked": !!item[col.key],
7762
+ onClick: () => col.onChange?.(item, !item[col.key]),
7763
+ className: cn(
7764
+ "relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7765
+ item[col.key] ? "bg-primary" : "bg-muted"
7766
+ ),
7767
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7768
+ "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
7769
+ item[col.key] ? "translate-x-4" : "translate-x-0"
7770
+ ) })
7771
+ }
7772
+ ) : col.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2", children: [
7773
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7774
+ "input",
7775
+ {
7776
+ type: "color",
7777
+ value: item[col.key] || "#000000",
7778
+ onChange: (e) => col.onChange?.(item, e.target.value),
7779
+ className: "h-7 w-7 cursor-pointer rounded border border-border bg-transparent p-0.5"
7780
+ }
7781
+ ),
7782
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground font-mono", children: item[col.key] })
7783
+ ] }) : col.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7784
+ Checkbox,
7785
+ {
7786
+ checked: !!item[col.key],
7787
+ onChange: (e) => col.onChange?.(item, e.target.checked)
7788
+ }
7789
+ ) : col.type === "text-url" ? (() => {
7790
+ const href = col.redirect ? typeof col.redirect === "function" ? col.redirect(item) : col.redirect : String(item[col.key] ?? "");
7791
+ const colorMap = {
7792
+ primary: "var(--primary)",
7793
+ info: "var(--info)",
7794
+ success: "var(--success)",
7795
+ warning: "var(--warning)",
7796
+ danger: "var(--danger)"
7797
+ };
7798
+ const underline = col.underlineColor ? colorMap[col.underlineColor] ?? col.underlineColor : "var(--primary)";
7799
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7800
+ "a",
7801
+ {
7802
+ href,
7803
+ target: col.openNewTab ? "_blank" : void 0,
7804
+ rel: col.openNewTab ? "noopener noreferrer" : void 0,
7805
+ style: { textDecorationColor: underline },
7806
+ className: "text-sm underline underline-offset-2 hover:opacity-75 transition-opacity break-all",
7807
+ onClick: col.openNewTab ? void 0 : (e) => e.preventDefault(),
7808
+ children: item[col.key]
7809
+ }
7810
+ );
7811
+ })() : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-foreground/90", children: item[col.key] }) }, `${String(col.key)}-${ci}`))
7812
+ ]
7813
+ }
7814
+ ),
7815
+ expandable && isExpanded && renderExpanded && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tr", { className: "bg-muted/20 border-b border-border/60", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { colSpan: allColumns.length + (selectable ? 1 : 0) + 1, className: "px-6 py-3", children: renderExpanded(item) }) })
7816
+ ] }, id);
7248
7817
  }) })
7249
7818
  ] }) }) }),
7250
- pagination && !serverPagination && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7819
+ clientPagination && !serverPagination && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7251
7820
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7252
7821
  "Showing ",
7253
7822
  (safePage - 1) * itemsPerPage + 1,
@@ -7298,8 +7867,8 @@ function Table({
7298
7867
  ] })
7299
7868
  ] }),
7300
7869
  serverPagination && (() => {
7301
- const { pagination: pagination2, currentPage: cp, goToPage } = serverPagination;
7302
- const totalServerPages = pagination2.last_page ?? Math.ceil(pagination2.total / pagination2.per_page);
7870
+ const { pagination, currentPage: cp, goToPage } = serverPagination;
7871
+ const totalServerPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
7303
7872
  const pills = [];
7304
7873
  if (totalServerPages <= 7) {
7305
7874
  for (let i = 1; i <= totalServerPages; i++) pills.push(i);
@@ -7312,7 +7881,7 @@ function Table({
7312
7881
  }
7313
7882
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7314
7883
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7315
- pagination2.total,
7884
+ pagination.total,
7316
7885
  " total rows \xB7 page ",
7317
7886
  cp,
7318
7887
  " of ",
@@ -7323,7 +7892,7 @@ function Table({
7323
7892
  "button",
7324
7893
  {
7325
7894
  onClick: () => goToPage(cp - 1),
7326
- disabled: !pagination2.prev_page_url,
7895
+ disabled: !pagination.prev_page_url,
7327
7896
  className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:pointer-events-none",
7328
7897
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronLeft, { className: "h-4 w-4" })
7329
7898
  }
@@ -7346,7 +7915,7 @@ function Table({
7346
7915
  "button",
7347
7916
  {
7348
7917
  onClick: () => goToPage(cp + 1),
7349
- disabled: !pagination2.next_page_url,
7918
+ disabled: !pagination.next_page_url,
7350
7919
  className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:pointer-events-none",
7351
7920
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronRight, { className: "h-4 w-4" })
7352
7921
  }
@@ -7361,6 +7930,7 @@ function Table({
7361
7930
  item: viewItem,
7362
7931
  fields: viewFields,
7363
7932
  width: defaultActions.modalWidth,
7933
+ grid: defaultActions.viewFormGrid,
7364
7934
  onClose: () => setViewItem(null)
7365
7935
  }
7366
7936
  ),